天天看点

WPF | 控制库| MultiSelectCombobox介绍资源NuGet软件包特征设计用法

目录

介绍

资源

NuGet软件包

特征

设计

依赖属性

解释LookUpContract(ILookUpContract)

ILookUpContract.cs

用法

1)简单场景

2)复杂场景

介绍

WPF具有允许用户选择多个项目的ListBox控件。但是,ListBox控件UI不具有对搜索/过滤的内置支持。开发人员必须采取变通办法来配置一个。此外,需要大量的鼠标交互。是的,您也许可以使用键盘完全完成所有操作。另一方面,Combobox具有非常好的UI,它支持搜索和过滤。但是,它不支持多选。

如果我们可以结合ListBox UI的行为和Combobox UI的优点呢?MultiSelectCombobox完全一样。它提供了具有多项选择的搜索/过滤功能。MultiSelectCombobox尝试模仿的ComboBox UI行为。

资源

GitHub—— https://github.com/nilayjoshi89/BlackPearl

NuGet软件包

  • https://www.nuget.org/packages/BlackPearl.Controls.Library/

特征

  • 内置对搜索和过滤的支持
  • 可扩展以支持复杂类型的自定义搜索和过滤
  • 能够添加不属于源集合的项目(通过LookUpContract复杂类型)
  • 易于使用!
WPF | 控制库| MultiSelectCombobox介绍资源NuGet软件包特征设计用法

设计

MultiSelectCombobox由RichTextBox,Popup和ListBox组成。RichTextBox中输入的文本是受到监视和操纵的。如果从源集合中找到合适的项,它将替换输入的文本选定项。所选项显示为TextBlock——内联UI元素。按键时,将显示弹出框,并显示与搜索条件匹配的项。如果集合中没有符合搜索条件的项,则不会显示。

依赖属性

控件被设计为公开使其工作所需的最小属性。

  1. ItemSource (IEnumerable)——源集合应绑定到此属性。它支持将简单类型收集为复杂类型/实体的字符串。
  2. SelectedItems (IList) ——此属性将提供用户选择的项的集合。
  3. ItemSeparator (char)——默认值为“ ;”。在控件中,项用ItemSeparator char分隔。如果项包含空格,这一点很重要。分离器应仔细选择。此外,为了在输入或指示控件基于当前输入的文本创建新项时指示项结束,使用了此字符。另外,如果用户输入的文本与集合中提供的任何项都不匹配或LookUpContract不支持从给定文本中创建对象,则将从控件UI中删除用户输入的文本。本文档后面将讨论对创建新项的支持。
  4. DisplayMemberPath (string)——如果ItemSource集合是类型复杂,开发人员可能需要重写类型的ToString()方法,否则可以定义DisplayMemberPath属性。默认值为string.Empty。
  5. SelectedItemTextBlockStyle (Style)——在RichTextBox文档中SelectedItems中显示为TextBlock的Inline元素。如果要更改所选TextBlock项的默认样式,则可以将一个新的Style分配给此属性。
  6. LookUpContract (ILookUpContract)——此属性用于自定义控件的搜索/过滤行为。控件提供了适用于大多数用户的默认实现。但是,在复杂类型和/或自定义行为的情况下,用户可以提供实现并更改控件行为。

解释LookUpContract(ILookUpContract)

默认搜索/过滤分别针对string.StartsWith&string.Equals。对于任何给定项,如果DisplayMemberPath未设置,则将item.ToString()值发送到过滤机制。如果DisplayMemberPath提供了,则通过项反射获取路径值,并将其发送到过滤器机制。这适用于大多数用户。

但是,如果用户需要自定义此设置/过滤机制,则他/她可以提供此接口的实现并绑定到LookUpContract属性。控制将尊重新约束的实现。

ILookUpContract.cs

public interface ILookUpContract
{
	// Whether contract supports creation of new object from user entered text
	bool SupportsNewObjectCreation { get; }
	
	// Method to check if item matches searchString
	bool IsItemMatchingSearchString(object sender, object item, string searchString);
			
	// Checks if item matches searchString or not
	bool IsItemEqualToString(object sender, object item, string seachString);
			
	// Creates object from provided string
	// This method need to be implemented only when SupportsNewObjectCreation is set to true
	object CreateObject(object sender, string searchString);
}
           
  • IsItemMatchingSearchString——调用此函数以过滤下拉列表中的建议项。用户输入的文本作为参数传递给该函数。返回true是否应该在给定文本的建议下拉列表中显示项。否则,返回false。
  • IsItemEqualToString ——此函数用于根据用户输入的文本从集合中查找项。
  • CreateObject——仅当SupportsNewObjectCreation设置为true时,才应实现此函数。调用此函数可根据提供的文本创建对象。另外,我们可以通过在以ItemSeparator结尾的控件中输入逗号分隔值来创建复杂对象。

例如,如果我们分配了具有两个属性Name和Age的复杂类型的Student集合。ItemSeparator设置为“;”。输入的文本可以是—— StudentNameHere、 ThisIsAgeValue。函数将接收此文本作为输入。函数应以string逗号分隔,然后将第一个值设置为Name,第二个值设置为Age属性并返回Student对象。这是一种实现方式。您可以按照用户想要的方式定义解析机制。

  • SupportsNewObjectCreation——如果将此属性设置为false,则控件将不允许用户选择提供的集合(ItemSource)以外的其他项。如果将此属性设置为true,控件将允许创建新对象。当控件应允许用户添加新对象时,这很有用。它还消除了创建单独的TextBox(es)和按钮以在现有SelectedItems/ItemSource中添加新项目的需要。

演示应用程序中提供了完整的示例。示例实现——AdvanceLookUpContract。

  • DefaultLookUpContract——如果没有提供新的实现给控件,则使用此DefaultLookUpContract实现。该契约使用string.StartsWith进行搜索和使用string.Equals进行比较。两种比较在文化和情况上都是不变的。

用法

Person的定义:

public class Person
{
    public string Name { get; set; }
    public string Company { get; internal set; }
    public string City { get; internal set; }
    public string Zip { get; internal set; }
    public string Info
    {
        get => $"{Name} - {Company}({Zip})";
    }
}
           

1)简单场景

我们在空间中设置Name值为DisplayMemberPath来显示Person的Name。我们要对Name属性执行简单的过滤。我们只需要提供ItemSource和SelectedItems收集即可控制功能。

WPF | 控制库| MultiSelectCombobox介绍资源NuGet软件包特征设计用法

.XAML代码

<controls:MultiSelectCombobox ItemSource="{Binding Source, Mode=TwoWay, 
                                          UpdateSourceTrigger=PropertyChanged}"

                                          SelectedItems="{Binding SelectedItems, 
                                          Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

                                          DisplayMemberPath="Name"

                                          ItemSeparator=";"/>
           

2)复杂场景

如果我们要对多个属性进行过滤或需要不同的搜索/过滤策略,并且/或者还希望支持从UI本身创建new一个Person 。

WPF | 控制库| MultiSelectCombobox介绍资源NuGet软件包特征设计用法

.XAML代码

<controls:MultiSelectCombobox ItemSource="{Binding Source, Mode=TwoWay, 
                                          UpdateSourceTrigger=PropertyChanged}"

                                          SelectedItems="{Binding SelectedItems2, 
                                          Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

                                          DisplayMemberPath="Info"

                                          ItemSeparator=";"

                                          LookUpContract="{Binding AdvanceLookUpContract}"/>
           

在XAML中,我们设置DisplayMemberPath为Info属性。Info设置为返回Name,Company和ZipCode。

AdvanceLookUpContract.cs

在此实现中,我们修改了搜索以尊重Person中的三个属性。如果这三个属性中的任何一个都包含搜索string,则项将显示在“建议”下拉列表中。项基于Name属性选自ItemSource。我们还设置SupportsNewObjectCreation为true意味着可以使用控件创建新Person对象。CreateObject用以解析格式为string{Name},{Company},{Zip}进的字符串。通过以ItemSeparator这种结尾的string格式输入,它将尝试根据输入的string内容创建一个对象。如果创建失败,它将从用户界面中删除用户输入的字符串。如果成功创建对象,它将在从UI中删除User输入的文本后将新创建的对象添加到UI和SelectedItem中。

public class AdvanceLookUpContract : MultiSelectCombobox.ILookUpContract
{
    public bool SupportsNewObjectCreation => true;

    public object CreateObject(object sender, string searchString)
    {
        if (searchString?.Count(c => c == ',') != 2)
        {
            return null;
        }

        var firstIndex = searchString.IndexOf(',');
        var lastIndex = searchString.LastIndexOf(',');

        return new Person()
        {
            Name = searchString.Substring(0, firstIndex),
            Company = searchString.Substring(firstIndex + 1, lastIndex - firstIndex - 1),
            Zip = searchString.Length >=lastIndex ? 
                  searchString.Substring(lastIndex + 1) : string.Empty
        };
    }

    public bool IsItemEqualToString(object sender, object item, string seachString)
    {
        if (!(item is Person std))
            return false;

        return string.Compare(seachString, std.Name, 
               System.StringComparison.InvariantCultureIgnoreCase) == 0;
    }

    public bool IsItemMatchingSearchString(object sender, object item, string searchString)
    {
        if (!(item is Person person))
            return false;

        if (string.IsNullOrEmpty(searchString))
            return true;

        return person.Name?.ToLower()?.Contains(searchString?.ToLower()) == true
            || person.Company.ToString().ToLower()?.Contains(searchString?.ToLower()) == true
            || person.Zip?.ToLower()?.Contains(searchString?.ToLower()) == true;
    }
}
           

有关完整的解决方案,请参阅演示应用程序。

继续阅读