Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Adding MultiSelectionComboBox #60

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;
using Avalonia.Labs.Catalog.Views;

namespace Avalonia.Labs.Catalog.ViewModels;

public class MultiSelectionComboBoxViewModel : ViewModelBase
{
static MultiSelectionComboBoxViewModel()
{
ViewLocator.Register(typeof(MultiSelectionComboBoxViewModel), () => new MultiSelectionComboBoxView());
}

public List<string> Items { get; } =
[
"Hello", "Avalonia"
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Avalonia.Labs.Catalog.Views.MultiSelectionComboBoxView">
<Grid ColumnDefinitions="*,3,*">
<ScrollViewer>
<ItemsControl x:Name="Properties">
</ItemsControl>
</ScrollViewer>

<controls:MultiSelectionComboBox Grid.Column="2" x:Name="MultiSelectionComboBox"
ItemsSource="{Binding Items}"/>
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Labs.Controls;
using Avalonia.Markup.Xaml;

namespace Avalonia.Labs.Catalog.Views;

public partial class MultiSelectionComboBoxView : UserControl
{
public MultiSelectionComboBoxView()
{
InitializeComponent();
}

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);

var mscb = this.Find<MultiSelectionComboBox>("MultiSelectionComboBox")!;
var properties = this.Find<ItemsControl>("Properties");

properties!.ItemsSource = new List<AvaloniaProperty>()
{
MultiSelectionComboBox.IsReadOnlyProperty,
MultiSelectionComboBox.IsEditableProperty
};

properties.ItemTemplate = new FuncDataTemplate<AvaloniaProperty>(
property => true,
property =>
{

if (property.PropertyType == typeof(bool))
{
var checkBox = new CheckBox() {Content = property.Name};
mscb.Bind(property, new Binding(nameof(checkBox.IsChecked), BindingMode.TwoWay));
return checkBox;
}

return new TextBox(){Text = "Unknown DataType"};
});
}
}
12 changes: 12 additions & 0 deletions samples/Avalonia.Labs.Catalog/Views/WelcomeView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Avalonia.Labs.Catalog.ViewModels"
xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
Expand Down Expand Up @@ -173,6 +174,17 @@
<Label FontSize="18"
VerticalAlignment="Center">Tab Layout</Label>
</Button>
<Button HorizontalAlignment="Stretch"
Height="60"
Command="{Binding NavigateTo}"
CornerRadius="10"
Margin="0,5">
<Button.CommandParameter>
<viewModels:MultiSelectionComboBoxViewModel/>
</Button.CommandParameter>
<Label FontSize="18"
VerticalAlignment="Center">MultiSelectionComboBox</Label>
</Button>
</StackPanel>
</Grid>
</ScrollViewer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Collections;
using Avalonia.Interactivity;

namespace Avalonia.Labs.Controls
{
/// <summary>
/// Provides data for the <see cref="MultiSelectionComboBox.AddedItemEvent"/>
/// </summary>
public class AddedItemEventArgs : RoutedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddedItemEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The AddedItemEvent</param>
/// <param name="source">The source object</param>
/// <param name="addedItem">The added object</param>
/// <param name="targetList">The target <see cref="IList"/> where the <see cref="AddedItem"/> should be added</param>
public AddedItemEventArgs(RoutedEvent routedEvent,
object source,
object? addedItem,
IList? targetList)
: base(routedEvent, source)
{
this.AddedItem = addedItem;
this.TargetList = targetList;
}

/// <summary>
/// Gets the added item
/// </summary>
public object? AddedItem { get; }

/// <summary>
/// Gets the <see cref="IList"/> where the <see cref="AddedItem"/> was added to
/// </summary>
public IList? TargetList { get; }
}

/// <summary>
/// RoutedEventHandler used for the <see cref="MultiSelectionComboBox.AddedItemEvent"/>.
/// </summary>
public delegate void AddedItemEventArgsHandler(object? sender, AddedItemEventArgs args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Collections;
using System.Globalization;
using Avalonia.Interactivity;

namespace Avalonia.Labs.Controls
{
/// <summary>
/// Provides data for the <see cref="MultiSelectionComboBox.AddingItemEvent"/>
/// </summary>
public class AddingItemEventArgs : RoutedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddingItemEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The AddingItemEvent</param>
/// <param name="source">The source object</param>
/// <param name="input">The input string to parse</param>
/// <param name="parsedObject">The parsed object</param>
/// <param name="accepted"><see langword="true"/> if the <see cref="ParsedObject"/> is accepted otherwise <see langword="false"/></param>
/// <param name="targetList">The target <see cref="IList"/> where the <see cref="ParsedObject"/> should be added</param>
/// <param name="targetType">the target <see cref="Type"/> to which the <see cref="Input"/> should be converted to</param>
/// <param name="stringFormat">the string format which can be used to control the <see cref="Parser"/></param>
/// <param name="culture">the culture which can be used to control the <see cref="Parser"/></param>
/// <param name="parser">The used parser</param>
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;
}

/// <summary>
/// The text input to parse
/// </summary>
public string? Input { get; }

/// <summary>
/// Gets or sets the parsed object to add. You can override it
/// </summary>
public object? ParsedObject { get; set; }

/// <summary>
/// Gets the string format which can be used to control the <see cref="Parser"/>
/// </summary>
public string? StringFormat { get; }

/// <summary>
/// Gets the culture which can be used to control the <see cref="Parser"/>
/// </summary>
public CultureInfo Culture { get; }

/// <summary>
/// Gets the <see cref="IParseStringToObject"/>-Instance which was used to parse the <see cref="Input"/> to the <see cref="ParsedObject"/>
/// </summary>
public IParseStringToObject? Parser { get; }

/// <summary>
/// Gets the target <see cref="Type"/> to which the <see cref="Input"/> should be converted to
/// </summary>
public Type? TargetType { get; }

/// <summary>
/// Gets the <see cref="IList"/> where the <see cref="ParsedObject"/> should be added
/// </summary>
public IList? TargetList { get; }

/// <summary>
/// Gets or sets whether the <see cref="ParsedObject"/> is accepted and can be added
/// </summary>
public bool Accepted { get; set; }
}

/// <summary>
/// RoutedEventHandler used for the <see cref="MultiSelectionComboBox.AddingItemEvent"/>.
/// </summary>
public delegate void AddingItemEventArgsHandler(object? sender, AddingItemEventArgs args);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;

namespace Avalonia.Labs.Controls
{
/// <summary>
/// This class is a helper class for the <see cref="MultiSelectionComboBox"/>.
/// It uses the <see cref="TypeConverter"/> for the elements <see cref="Type"/>. If you need more control
/// over the conversion you should create your own class which implements <see cref="IParseStringToObject"/>
/// </summary>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Hello World")]
#endif
public class DefaultStringToObjectParser : IParseStringToObject
{
public static readonly DefaultStringToObjectParser Instance = new();

/// <inheritdoc />
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;
}
}

/// <summary>
/// Tries to get the elements <see cref="Type"/> for a given <see cref="IEnumerable"/>
/// </summary>
/// <param name="list">Any collection of elements</param>
/// <returns>the elements <see cref="Type"/></returns>
public Type? GetElementType(IEnumerable? list)
{
if (list is null)
{
return null;
}

var listType = list.GetType();

return listType.IsGenericType ? listType.GetGenericArguments().FirstOrDefault() : listType.GetElementType();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;

namespace Avalonia.Labs.Controls
{
/// <summary>
/// Defines a function that is used to check if a given string represents a given object of any type.
/// </summary>
public interface ICompareObjectToString
{
/// <summary>
/// Checks if the given input string matches to the given object
/// </summary>
/// <param name="input">The string to compare</param>
/// <param name="objectToCompare">The object to compare</param>
/// <param name="stringComparison">The <see cref="StringComparison"/> used to check if the string matches</param>
/// <param name="stringFormat">The string format to apply</param>
/// <returns>true if the string represents the object, otherwise false.</returns>
public bool CheckIfStringMatchesObject(string? input, object? objectToCompare, StringComparison stringComparison, string? stringFormat);
}

public class DefaultObjectToStringComparer : ICompareObjectToString
{
public static DefaultObjectToStringComparer Instance { get; } = new();

/// <inheritdoc/>
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);
}
}
}
Loading