diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml
new file mode 100644
index 0000000000..68405a5b8d
--- /dev/null
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml.cs b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml.cs
new file mode 100644
index 0000000000..2407fee394
--- /dev/null
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/ExampleViews/MultiSelectionComboBoxExample.xaml.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using MahApps.Metro.Controls;
+using MahApps.Metro.Controls.Dialogs;
+using System.Diagnostics;
+using System.Windows.Controls;
+
+namespace MetroDemo.ExampleViews
+{
+ ///
+ /// Interaction logic for MultiSelectionComboBoxExample.xaml
+ ///
+ public partial class MultiSelectionComboBoxExample : UserControl
+ {
+ public MultiSelectionComboBoxExample()
+ {
+ this.InitializeComponent();
+ }
+
+ private void Mscb_Example_AddingItem(object? sender, AddingItemEventArgs args)
+ {
+ // We don't want to get double entries so let`s check if we already have one.
+ args.Accepted = args.TargetList is not null && !args.TargetList.Contains(args.ParsedObject);
+ }
+
+ private async void Mscb_Example_AddedItem(object? sender, AddedItemEventArgs args)
+ {
+ var window = this.TryFindParent();
+ if (window is null)
+ {
+ return;
+ }
+
+ await window.ShowMessageAsync("Added Item", $"Successfully added \"{args.AddedItem}\" to your Items-Collection");
+ }
+
+ private void mscb_Example_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ foreach (var item in e.AddedItems)
+ {
+ Debug.WriteLine($"MultiSelectionComboBox-Example: Selected item \"{item}\"");
+ }
+
+ foreach (var item in e.RemovedItems)
+ {
+ Debug.WriteLine($"MultiSelectionComboBox-Example: Unselected item \"{item}\"");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MainWindow.xaml b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MainWindow.xaml
index a524d41798..e381e887f0 100644
--- a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MainWindow.xaml
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MainWindow.xaml
@@ -332,6 +332,10 @@
+
+
+
+
(x => x is not null && x.CanUseToggleSwitch,
async x => { await this._dialogCoordinator.ShowMessageAsync(this, "ToggleSwitch", "The ToggleSwitch is now Off."); });
+
+ this.MyObjectParser = new ObjectParser(this, this._dialogCoordinator);
}
public ICommand ArtistsDropDownCommand { get; }
@@ -551,5 +553,168 @@ private void ToggleIconScaling(MultiFrameImageMode? multiFrameImageMode)
public bool IsNoScaleSmallerFrame => ((MetroWindow)Application.Current.MainWindow).IconScalingMode == MultiFrameImageMode.NoScaleSmallerFrame;
public bool IsToggleSwitchVisible { get; set; }
+
+ public ObservableCollection Animals { get; } = new()
+ {
+ "African elephant",
+ "Ant",
+ "Antelope",
+ "Aphid",
+ "Arctic wolf",
+ "Badger",
+ "Bald eagle",
+ "Bat",
+ "Bear",
+ "Bee",
+ "Beetle",
+ "Bengal tiger",
+ "Bison",
+ "Butterfly",
+ "Camel",
+ "Cat",
+ "Caterpillar",
+ "Chicken",
+ "Chimpanzee",
+ "Chipmunk",
+ "Cicada",
+ "Clam",
+ "Cockroach",
+ "Cormorant",
+ "Cow",
+ "Coyote",
+ "Crab",
+ "Crow",
+ "Cuckoo",
+ "Deer",
+ "Dog",
+ "Dolphin",
+ "Donkey",
+ "Dove",
+ "Dragonfly",
+ "Duck",
+ "Elephant",
+ "Elk",
+ "Finch",
+ "Fish",
+ "Flamingo",
+ "Flea",
+ "Fly",
+ "Fox",
+ "Frigatebird",
+ "Giraffe",
+ "Goat",
+ "Goldfish",
+ "Goose",
+ "Gorilla",
+ "Grasshopper",
+ "Great horned owl",
+ "Guinea pig",
+ "Hamster",
+ "Hare",
+ "Hawk",
+ "Hedgehog",
+ "Hippopotamus",
+ "Hornbill",
+ "Horse",
+ "Horse-fly",
+ "Howler monkey",
+ "Hummingbird",
+ "Hyena",
+ "Ibis",
+ "Jackal",
+ "Jellyfish",
+ "Kangaroo",
+ "Koala",
+ "Ladybugs(NAmE) /ladybirds(BrE)",
+ "Leopard",
+ "Lion",
+ "Lizard",
+ "Lobster",
+ "Lynxes",
+ "Mantis",
+ "Marten",
+ "Mole",
+ "Monkey",
+ "Mosquito",
+ "Moth",
+ "Mouse",
+ "Octopus",
+ "Okapi",
+ "Orangutan",
+ "Otter",
+ "Owl",
+ "Ox",
+ "Oyster",
+ "Panda",
+ "Parrot",
+ "Pelecaniformes",
+ "Pelican",
+ "Penguin",
+ "Pig",
+ "Pigeon",
+ "Porcupine",
+ "Possum",
+ "Puma",
+ "Rabbit",
+ "Raccoon",
+ "Rat",
+ "Raven",
+ "Red dear",
+ "Red panda",
+ "Red squirrel",
+ "Reindeer",
+ "Rhinoceros",
+ "Robin",
+ "Sandpiper",
+ "Sea turtle",
+ "Seahorse",
+ "Seal",
+ "Shark",
+ "Sheep",
+ "Shell",
+ "Shrimp",
+ "Snake",
+ "Sparrow",
+ "Squid",
+ "Squirrel",
+ "Squirrel monkey",
+ "Starfish",
+ "Stork",
+ "Swallow",
+ "Swan",
+ "Termite",
+ "Tern",
+ "Tick",
+ "Tiger",
+ "Turkey",
+ "Turtle",
+ "Walrus",
+ "Wasp",
+ "Whale",
+ "Whitefly",
+ "Wild boar",
+ "Wolf",
+ "Wombat",
+ "Woodpecker",
+ "Zebra"
+ };
+
+ public ObservableCollection SelectedAnimals { get; } = new()
+ {
+ "Dog",
+ "Cat",
+ "Zebra"
+ };
+
+ private object? myFavoriteAnimal;
+
+ [Display(Prompt = "Select your favorite animal(s)")]
+ public object? MyFavoriteAnimal
+ {
+ get => this.myFavoriteAnimal;
+ set => this.Set(ref this.myFavoriteAnimal, value);
+ }
+
+ public ObjectParser? MyObjectParser { get; }
}
}
\ No newline at end of file
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Album.cs b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Album.cs
index 44c1029908..d1d28cbbc2 100644
--- a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Album.cs
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Album.cs
@@ -73,6 +73,12 @@ public virtual Artist? Artist
get => this._artist;
set => this.Set(ref this._artist, value);
}
+
+ public override string ToString()
+ {
+ return $"{this.Artist}: {this.Title} ({this.Price.ToString("C")})";
+ }
+
}
public static class SampleData
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Artist.cs b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Artist.cs
index 5270392f8b..2a388031d7 100644
--- a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Artist.cs
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Artist.cs
@@ -30,5 +30,14 @@ public List? Albums
get => this._albums;
set => this.Set(ref this._albums, value);
}
+
+#if NET5_0_OR_GREATER || NETCOREAPP
+ public override string? ToString()
+#else
+ public override string ToString()
+#endif
+ {
+ return this.Name ?? base.ToString();
+ }
}
}
\ No newline at end of file
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Genre.cs b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Genre.cs
index badca7b796..a35ba9d05f 100644
--- a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Genre.cs
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/Genre.cs
@@ -37,5 +37,14 @@ public List? Albums
get => this._albums;
set => this.Set(ref this._albums, value);
}
+
+#if NET5_0_OR_GREATER || NETCOREAPP
+ public override string? ToString()
+#else
+ public override string ToString()
+#endif
+ {
+ return this.Name ?? base.ToString();
+ }
}
}
\ No newline at end of file
diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/ObjectParser.cs b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/ObjectParser.cs
new file mode 100644
index 0000000000..0cfcbabf4c
--- /dev/null
+++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/Models/ObjectParser.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using MahApps.Metro.Controls;
+using MahApps.Metro.Controls.Dialogs;
+using System;
+using System.Globalization;
+
+namespace MetroDemo.Models
+{
+ public class ObjectParser : IParseStringToObject
+ {
+ private readonly MainWindowViewModel _mainWindowViewModel;
+ private readonly IDialogCoordinator _dialogCoordinator;
+
+ public ObjectParser(MainWindowViewModel mainWindowViewModel, IDialogCoordinator dialogCoordinator)
+ {
+ this._mainWindowViewModel = mainWindowViewModel;
+ this._dialogCoordinator = dialogCoordinator;
+ }
+
+ ///
+ public bool TryCreateObjectFromString(string? input,
+ out object? result,
+ CultureInfo? culture = null,
+ string? stringFormat = null,
+ Type? elementType = null)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ result = null;
+ return false;
+ }
+
+ MetroDialogSettings dialogSettings = new MetroDialogSettings
+ {
+ AffirmativeButtonText = "Yes",
+ NegativeButtonText = "No",
+ DefaultButtonFocus = MessageDialogResult.Affirmative
+ };
+
+ if (this._dialogCoordinator.ShowModalMessageExternal(this._mainWindowViewModel, "Add Animal", $"Do you want to add \"{input}\" to the animals list?", MessageDialogStyle.AffirmativeAndNegative, dialogSettings) == MessageDialogResult.Affirmative)
+ {
+ result = input!.Trim();
+ return true;
+ }
+ else
+ {
+ result = null;
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/Helper/BindingHelper.cs b/src/MahApps.Metro/Controls/Helper/BindingHelper.cs
new file mode 100644
index 0000000000..c4c8c62a3b
--- /dev/null
+++ b/src/MahApps.Metro/Controls/Helper/BindingHelper.cs
@@ -0,0 +1,129 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MahApps.Metro.Controls.Helper
+{
+ ///
+ /// A helper class to evaluate Bindings in code behind
+ ///
+ public static class BindingHelper
+ {
+ ///
+ /// A dummy property to initialize the binding to evaluate
+ ///
+ private static readonly DependencyProperty DummyProperty
+ = DependencyProperty.RegisterAttached("Dummy",
+ typeof(object),
+ typeof(BindingHelper),
+ new UIPropertyMetadata(null));
+
+ ///
+ /// A dummy property to initialize the binding to evaluate. This property supports also string format.
+ ///
+ private static readonly DependencyProperty DummyTextProperty
+ = DependencyProperty.RegisterAttached("DummyText",
+ typeof(string),
+ typeof(BindingHelper),
+ new UIPropertyMetadata(null));
+
+ ///
+ /// Evaluates a defined -path on the given object
+ ///
+ /// the object to evaluate
+ /// the binding expression to evaluate
+ /// the result of the
+ public static object? Eval(object? source, string? expression)
+ {
+ Binding binding = new Binding(expression) { Source = source };
+ return Eval(binding);
+ }
+
+ ///
+ /// Evaluates a defined -path on the given object
+ ///
+ /// the object to evaluate
+ /// the binding expression to evaluate
+ /// the string format to use
+ /// the result of the
+ public static object? Eval(object? source, string? expression, string? format)
+ {
+ Binding binding = new Binding(expression) { Source = source, StringFormat = format };
+ return Eval(binding);
+ }
+
+ ///
+ /// Evaluates a defined on the given object
+ ///
+ /// The to evaluate
+ /// the object to evaluate
+ ///
+ public static object? Eval(Binding? binding, object? source)
+ {
+ if (binding is null)
+ {
+ throw new ArgumentNullException(nameof(binding));
+ }
+
+ Binding newBinding = new Binding
+ {
+ Source = source,
+ AsyncState = binding.AsyncState,
+ BindingGroupName = binding.BindingGroupName,
+ BindsDirectlyToSource = binding.BindsDirectlyToSource,
+ Path = binding.Path,
+ Converter = binding.Converter,
+ ConverterCulture = binding.ConverterCulture,
+ ConverterParameter = binding.ConverterParameter,
+ FallbackValue = binding.FallbackValue,
+ IsAsync = binding.IsAsync,
+ Mode = BindingMode.OneWay,
+ StringFormat = binding.StringFormat,
+ TargetNullValue = binding.TargetNullValue
+ };
+
+ return Eval(newBinding);
+ }
+
+ ///
+ /// Evaluates a defined on the given
+ ///
+ /// The to evaluate
+ /// optional: The to evaluate
+ /// The resulting object
+ public static object? Eval(Binding? binding, DependencyObject? dependencyObject)
+ {
+ if (binding is null)
+ {
+ throw new ArgumentNullException(nameof(binding));
+ }
+
+ dependencyObject ??= new DependencyObject();
+
+ if (string.IsNullOrEmpty(binding.StringFormat))
+ {
+ BindingOperations.SetBinding(dependencyObject, DummyProperty, binding);
+ return dependencyObject.GetValue(DummyProperty);
+ }
+ else
+ {
+ BindingOperations.SetBinding(dependencyObject, DummyTextProperty, binding);
+ return dependencyObject.GetValue(DummyTextProperty);
+ }
+ }
+
+ ///
+ /// Evaluates a defined on the given
+ ///
+ /// The to evaluate
+ /// The resulting object
+ public static object? Eval(Binding? binding)
+ {
+ return Eval(binding, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/Helper/ComboBoxHelper.cs b/src/MahApps.Metro/Controls/Helper/ComboBoxHelper.cs
index 42988cf369..e74b499421 100644
--- a/src/MahApps.Metro/Controls/Helper/ComboBoxHelper.cs
+++ b/src/MahApps.Metro/Controls/Helper/ComboBoxHelper.cs
@@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using MahApps.Metro.ValueBoxes;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Input;
namespace MahApps.Metro.Controls
{
@@ -66,5 +68,57 @@ public static void SetCharacterCasing(UIElement obj, CharacterCasing value)
{
obj.SetValue(CharacterCasingProperty, value);
}
+
+
+ /// Identifies the InterceptMouseWheelSelection attached dependcy property.
+ public static readonly DependencyProperty InterceptMouseWheelSelectionProperty = DependencyProperty.RegisterAttached("InterceptMouseWheelSelection", typeof(bool), typeof(ComboBoxHelper), new PropertyMetadata(BooleanBoxes.TrueBox, OnInterceptMouseWheelSelectionPropertyChangedCallback));
+
+
+ ///
+ /// Gets if the given accepts selection via mouse wheel.
+ ///
+ [Category(AppName.MahApps)]
+ [AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ public static bool GetInterceptMouseWheelSelection(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(InterceptMouseWheelSelectionProperty);
+ }
+
+ ///
+ /// Sets if the given accepts selection via mouse wheel. The default is true.
+ ///
+ [Category(AppName.MahApps)]
+ [AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ public static void SetInterceptMouseWheelSelection(ComboBox obj, bool value)
+ {
+ obj.SetValue(InterceptMouseWheelSelectionProperty, BooleanBoxes.Box(value));
+ }
+
+ private static void OnInterceptMouseWheelSelectionPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ComboBox comboBox && e.NewValue != e.OldValue && e.NewValue is bool)
+ {
+ comboBox.PreviewMouseWheel -= ComboBox_PreviewMouseWheel;
+
+ // If this property is set to false we need to handle the MouseWheel before the ComboBox does.
+ if (!((bool)e.NewValue))
+ {
+ comboBox.PreviewMouseWheel += ComboBox_PreviewMouseWheel;
+ }
+ }
+ }
+
+ private static void ComboBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ if (sender is MultiSelectionComboBox)
+ {
+ // This will be handled by MultiSelectionComboBox directly so we don't need to do anything here.
+ }
+ else if (sender is ComboBox comboBox)
+ {
+ // mark the event as handled to cancel selection. We should not handle it if the drop down is open.
+ e.Handled = !comboBox.IsDropDownOpen;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/Helper/MultiSelectorHelper.cs b/src/MahApps.Metro/Controls/Helper/MultiSelectorHelper.cs
index 11b753d6f9..06d6395ef2 100644
--- a/src/MahApps.Metro/Controls/Helper/MultiSelectorHelper.cs
+++ b/src/MahApps.Metro/Controls/Helper/MultiSelectorHelper.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -30,9 +30,9 @@ public static readonly DependencyProperty SelectedItemsProperty
///
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- if (!(d is ListBox || d is MultiSelector))
+ if (!(d is ListBox || d is MultiSelector || d is MultiSelectionComboBox))
{
- throw new ArgumentException("The property 'SelectedItems' may only be set on ListBox or MultiSelector elements.");
+ throw new ArgumentException("The property 'SelectedItems' may only be set on ListBox, MultiSelector or MultiSelectionComboBox elements.");
}
if (e.OldValue != e.NewValue)
@@ -135,6 +135,17 @@ public MultiSelectorBinding(Selector selector, IList collection)
multiSelector.SelectedItems.Add(newItem);
}
}
+ else if (selector is MultiSelectionComboBox multiSelectionComboBox)
+ {
+ if (multiSelectionComboBox.SelectedItems is not null)
+ {
+ multiSelectionComboBox.SelectedItems.Clear();
+ foreach (var newItem in collection)
+ {
+ multiSelectionComboBox.SelectedItems.Add(newItem);
+ }
+ }
+ }
}
///
diff --git a/src/MahApps.Metro/Controls/Helper/TextBoxHelper.cs b/src/MahApps.Metro/Controls/Helper/TextBoxHelper.cs
index 91f4a10e2f..544ee83822 100644
--- a/src/MahApps.Metro/Controls/Helper/TextBoxHelper.cs
+++ b/src/MahApps.Metro/Controls/Helper/TextBoxHelper.cs
@@ -77,6 +77,7 @@ public static readonly DependencyProperty WatermarkProperty
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -91,6 +92,7 @@ public static string GetWatermark(DependencyObject obj)
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -118,6 +120,7 @@ public static readonly DependencyProperty WatermarkAlignmentProperty
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -135,6 +138,7 @@ public static TextAlignment GetWatermarkAlignment(DependencyObject obj)
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -162,6 +166,7 @@ public static readonly DependencyProperty WatermarkTrimmingProperty
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -179,6 +184,7 @@ public static TextTrimming GetWatermarkTrimming(DependencyObject obj)
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -229,6 +235,7 @@ public static readonly DependencyProperty UseFloatingWatermarkProperty
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
[AttachedPropertyBrowsableForType(typeof(HotKeyBox))]
[AttachedPropertyBrowsableForType(typeof(ColorPicker))]
@@ -241,6 +248,7 @@ public static bool GetUseFloatingWatermark(DependencyObject obj)
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
[AttachedPropertyBrowsableForType(typeof(HotKeyBox))]
[AttachedPropertyBrowsableForType(typeof(ColorPicker))]
@@ -328,6 +336,7 @@ private static void OnControlWithAutoWatermarkSupportLoaded(object o, RoutedEven
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -355,6 +364,7 @@ public static bool GetAutoWatermark(DependencyObject element)
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(TimePickerBase))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
@@ -376,7 +386,8 @@ private static readonly Dictionary AutoWatermarkProper
{ typeof(TimePicker), TimePickerBase.SelectedDateTimeProperty },
{ typeof(DateTimePicker), TimePickerBase.SelectedDateTimeProperty },
{ typeof(ColorPicker), ColorPickerBase.SelectedColorProperty },
- { typeof(ColorCanvas), ColorPickerBase.SelectedColorProperty }
+ { typeof(ColorCanvas), ColorPickerBase.SelectedColorProperty },
+ { typeof(MultiSelectionComboBox), MultiSelectionComboBox.SelectedItemProperty }
};
internal static readonly DependencyPropertyKey TextLengthPropertyKey
@@ -408,6 +419,7 @@ public static readonly DependencyProperty HasTextProperty
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
public static bool GetHasText(DependencyObject obj)
@@ -419,6 +431,7 @@ public static bool GetHasText(DependencyObject obj)
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
[AttachedPropertyBrowsableForType(typeof(PasswordBox))]
[AttachedPropertyBrowsableForType(typeof(ComboBox))]
+ [AttachedPropertyBrowsableForType(typeof(MultiSelectionComboBox))]
[AttachedPropertyBrowsableForType(typeof(DatePicker))]
[AttachedPropertyBrowsableForType(typeof(NumericUpDown))]
public static void SetHasText(DependencyObject obj, bool value)
@@ -1050,7 +1063,7 @@ public static void ButtonClicked(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
- var parent = button.GetAncestors().FirstOrDefault(a => a is RichTextBox || a is TextBox || a is PasswordBox || a is ComboBox || a is ColorPickerBase);
+ var parent = button.GetAncestors().FirstOrDefault(a => a is RichTextBox || a is TextBox || a is PasswordBox || a is ComboBox || a is ColorPickerBase || a is MultiSelectionComboBox);
if (parent is null)
{
return;
@@ -1085,6 +1098,29 @@ public static void ButtonClicked(object sender, RoutedEventArgs e)
passwordBox.Clear();
passwordBox.GetBindingExpression(PasswordBoxBindingBehavior.PasswordProperty)?.UpdateSource();
}
+ else if (parent is MultiSelectionComboBox multiSelectionComboBox)
+ {
+ if (multiSelectionComboBox.HasCustomText)
+ {
+ multiSelectionComboBox.ResetEditableText(true);
+ }
+ else
+ {
+ switch (multiSelectionComboBox.SelectionMode)
+ {
+ case SelectionMode.Single:
+ multiSelectionComboBox.SetCurrentValue(MultiSelectionComboBox.SelectedItemProperty, null);
+ break;
+ case SelectionMode.Multiple:
+ case SelectionMode.Extended:
+ multiSelectionComboBox.SelectedItems?.Clear();
+ break;
+ default:
+ throw new NotSupportedException("Unknown SelectionMode");
+ }
+ multiSelectionComboBox.ResetEditableText(true);
+ }
+ }
else if (parent is ComboBox comboBox)
{
if (comboBox.IsEditable)
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddedItemEventArgs.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddedItemEventArgs.cs
new file mode 100644
index 0000000000..b817ee55e0
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddedItemEventArgs.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Windows;
+
+namespace MahApps.Metro.Controls
+{
+ ///
+ /// Provides data for the
+ ///
+ public class AddedItemEventArgs : RoutedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The AddedItemEvent
+ /// The source object
+ /// The added object
+ /// The target where the should be added
+ public AddedItemEventArgs(RoutedEvent routedEvent,
+ object source,
+ object? addedItem,
+ IList? targetList)
+ : base(routedEvent, source)
+ {
+ this.AddedItem = addedItem;
+ this.TargetList = targetList;
+ }
+
+ ///
+ /// Gets the added item
+ ///
+ public object? AddedItem { get; }
+
+ ///
+ /// Gets the where the was added to
+ ///
+ public IList? TargetList { get; }
+ }
+
+ ///
+ /// RoutedEventHandler used for the .
+ ///
+ public delegate void AddedItemEventArgsHandler(object? sender, AddedItemEventArgs args);
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddingItemEventArgs.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddingItemEventArgs.cs
new file mode 100644
index 0000000000..05c85d310b
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/AddingItemEventArgs.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Windows;
+
+namespace MahApps.Metro.Controls
+{
+ ///
+ /// Provides data for the
+ ///
+ public class AddingItemEventArgs : RoutedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The AddingItemEvent
+ /// The source object
+ /// The input string to parse
+ /// The parsed object
+ /// if the is accepted otherwise
+ /// The target where the should be added
+ /// the target to which the should be converted to
+ /// the string format which can be used to control the
+ /// the culture which can be used to control the
+ /// The used parser
+ public AddingItemEventArgs(RoutedEvent routedEvent,
+ object source,
+ string? input,
+ object? parsedObject,
+ bool accepted,
+ IList? targetList,
+ Type? targetType,
+ string? stringFormat,
+ CultureInfo culture,
+ IParseStringToObject? parser)
+ : base(routedEvent, source)
+ {
+ this.Input = input;
+ this.ParsedObject = parsedObject;
+ this.Accepted = accepted;
+ this.TargetList = targetList;
+ this.TargetType = targetType;
+ this.StringFormat = stringFormat;
+ this.Culture = culture;
+ this.Parser = parser;
+ }
+
+ ///
+ /// The text input to parse
+ ///
+ public string? Input { get; }
+
+ ///
+ /// Gets or sets the parsed object to add. You can override it
+ ///
+ public object? ParsedObject { get; set; }
+
+ ///
+ /// Gets the string format which can be used to control the
+ ///
+ public string? StringFormat { get; }
+
+ ///
+ /// Gets the culture which can be used to control the
+ ///
+ public CultureInfo Culture { get; }
+
+ ///
+ /// Gets the -Instance which was used to parse the to the
+ ///
+ public IParseStringToObject? Parser { get; }
+
+ ///
+ /// Gets the target to which the should be converted to
+ ///
+ public Type? TargetType { get; }
+
+ ///
+ /// Gets the where the should be added
+ ///
+ public IList? TargetList { get; }
+
+ ///
+ /// Gets or sets whether the is accepted and can be added
+ ///
+ public bool Accepted { get; set; }
+ }
+
+ ///
+ /// RoutedEventHandler used for the .
+ ///
+ public delegate void AddingItemEventArgsHandler(object? sender, AddingItemEventArgs args);
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/DefaultStringToObjectParser.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/DefaultStringToObjectParser.cs
new file mode 100644
index 0000000000..de7438a510
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/DefaultStringToObjectParser.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+
+namespace MahApps.Metro.Controls
+{
+ ///
+ /// This class is a helper class for the .
+ /// It uses the for the elements . If you need more control
+ /// over the conversion you should create your own class which implements
+ ///
+ public class DefaultStringToObjectParser : IParseStringToObject
+ {
+ public static readonly DefaultStringToObjectParser Instance = new();
+
+ ///
+ public bool TryCreateObjectFromString(string? input,
+ out object? result,
+ CultureInfo? culture = null,
+ string? stringFormat = null,
+ Type? targetType = null)
+ {
+ try
+ {
+ // If the input is null the result is also null
+ if (input is null)
+ {
+ result = null;
+ return true;
+ }
+
+ // If we don't know the target type we cannot convert in this class.
+ // Either provide the target type or roll your own class implementing IParseStringToObject
+ if (targetType is null)
+ {
+ result = null;
+ return false;
+ }
+
+ result = TypeDescriptor.GetConverter(targetType).ConvertFromString(default!, culture ?? CultureInfo.InvariantCulture, input);
+ return true;
+ }
+ catch
+ {
+ result = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Tries to get the elements for a given
+ ///
+ /// Any collection of elements
+ /// the elements
+ public Type? GetElementType(IEnumerable? list)
+ {
+ if (list is null)
+ {
+ return null;
+ }
+
+ var listType = list.GetType();
+
+ return listType.IsGenericType ? listType.GetGenericArguments().FirstOrDefault() : listType.GetElementType();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/ICompareObjectToString.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/ICompareObjectToString.cs
new file mode 100644
index 0000000000..4000344a3e
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/ICompareObjectToString.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Windows.Markup;
+
+namespace MahApps.Metro.Controls
+{
+ ///
+ /// Defines a function that is used to check if a given string represents a given object of any type.
+ ///
+ public interface ICompareObjectToString
+ {
+ ///
+ /// Checks if the given input string matches to the given object
+ ///
+ /// The string to compare
+ /// The object to compare
+ /// The used to check if the string matches
+ /// The string format to apply
+ /// true if the string represents the object, otherwise false.
+ public bool CheckIfStringMatchesObject(string? input, object? objectToCompare, StringComparison stringComparison, string? stringFormat);
+ }
+
+ [MarkupExtensionReturnType(typeof(DefaultObjectToStringComparer))]
+ public class DefaultObjectToStringComparer : MarkupExtension, ICompareObjectToString
+ {
+ ///
+ public bool CheckIfStringMatchesObject(string? input, object? objectToCompare, StringComparison stringComparison, string? stringFormat)
+ {
+ if (input is null)
+ {
+ return objectToCompare is null;
+ }
+
+ if (objectToCompare is null)
+ {
+ return false;
+ }
+
+ string? objectText;
+ if (string.IsNullOrEmpty(stringFormat))
+ {
+ objectText = objectToCompare.ToString();
+ }
+ else if (stringFormat!.Contains("{") && stringFormat!.Contains("}"))
+ {
+ objectText = string.Format(stringFormat!, objectToCompare!);
+ }
+ else
+ {
+ objectText = string.Format($"{{0:{stringFormat}}}", objectToCompare);
+ }
+
+ return input.Equals(objectText, stringComparison);
+ }
+
+ ///
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/IParseStringToObject.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/IParseStringToObject.cs
new file mode 100644
index 0000000000..c21b1d4bbd
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/IParseStringToObject.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+
+namespace MahApps.Metro.Controls
+{
+ ///
+ /// This interfaces is used to parse a string to any object.
+ ///
+ public interface IParseStringToObject
+ {
+ ///
+ /// Parses the given input to an object of the given type.
+ ///
+ /// The input string to parse
+ /// The object if successful, otherwise null
+ /// The culture which should be used to parse. This parameter is optional
+ /// The string format to apply. This parameter is optional
+ /// the to which the input should be converted to. This parameter is optional
+ /// if converting successful, otherwise
+ bool TryCreateObjectFromString(string? input,
+ out object? result,
+ CultureInfo? culture = null,
+ string? stringFormat = null,
+ Type? targetType = null);
+ }
+}
\ No newline at end of file
diff --git a/src/MahApps.Metro/Controls/MultiSelectionComboBox/MultiSelectionComboBox.cs b/src/MahApps.Metro/Controls/MultiSelectionComboBox/MultiSelectionComboBox.cs
new file mode 100644
index 0000000000..985b31ff41
--- /dev/null
+++ b/src/MahApps.Metro/Controls/MultiSelectionComboBox/MultiSelectionComboBox.cs
@@ -0,0 +1,1992 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using ControlzEx;
+using MahApps.Metro.Controls.Helper;
+using MahApps.Metro.ValueBoxes;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+using JetBrains.Annotations;
+
+namespace MahApps.Metro.Controls
+{
+ [TemplatePart(Name = nameof(PART_PopupListBox), Type = typeof(ListBox))]
+ [TemplatePart(Name = nameof(PART_Popup), Type = typeof(Popup))]
+ [TemplatePart(Name = nameof(PART_SelectedItemsPresenter), Type = typeof(ListBox))]
+ [StyleTypedProperty(Property = nameof(SelectedItemContainerStyle), StyleTargetType = typeof(ListBoxItem))]
+ [StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(ListBoxItem))]
+ public class MultiSelectionComboBox : ComboBox
+ {
+ #region Constructors
+
+ static MultiSelectionComboBox()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiSelectionComboBox), new FrameworkPropertyMetadata(typeof(MultiSelectionComboBox)));
+ TextProperty.OverrideMetadata(typeof(MultiSelectionComboBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnTextChanged));
+ CommandManager.RegisterClassCommandBinding(typeof(MultiSelectionComboBox), new CommandBinding(ClearContentCommand, ExecutedClearContentCommand, CanExecuteClearContentCommand));
+ CommandManager.RegisterClassCommandBinding(typeof(MultiSelectionComboBox), new CommandBinding(RemoveItemCommand, RemoveItemCommand_Executed, RemoveItemCommand_CanExecute));
+ CommandManager.RegisterClassCommandBinding(typeof(MultiSelectionComboBox), new CommandBinding(SelectAllCommand, OnSelectAll, OnQueryStatusSelectAll));
+ }
+
+ public MultiSelectionComboBox()
+ {
+ var selectedItemsImpl = new ObservableCollection