DataTemplate中TextSearch.Text的用法

7
我有一个非常简单的例子:WPF窗体应用程序,其中包含一个带有数据的字典的单个表单:

Dim dict As New Collections.Generic.Dictionary(Of String, String)

Private Sub MainWindow_Loaded() Handles Me.Loaded
    dict.Add("One", "1")
    dict.Add("Two", "2")
    dict.Add("Three", "3")

    lst1.ItemsSource = dict
End Sub

我有一个表格,其中包含一个名为“lst1”的ListBox,它使用“dict”作为项目源:

<ListBox x:Name="lst1">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Label Content="{Binding Value}" 
                   TextSearch.Text="{Binding Path=Key, Mode=OneWay}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

此外,我有一个非绑定 ListBox,手动填充了一些值:

<ListBox>
    <Label TextSearch.Text="One" Content="1" />
    <Label TextSearch.Text="Two" Content="2" />
    <Label TextSearch.Text="Three" Content="3" />
</ListBox>

当我启动应用程序时,它看起来像这样:
THE QUESTION:
如果我尝试使用键盘通过键入“one”、“two”或“three”来导航项目,则只能在未绑定的列表框中成功。绑定列表框失败。
一些备注: 1.) 如果我在绑定的列表框中按“[”,焦点会以循环方式从项到项进行更改:从1到2,从2到3,从3到1,再从1到2等。 2.) 我已经使用Snoop检查了应用程序。我发现绑定和非绑定列表框之间的一个区别。两个列表框都在ItemsPresenter中设置了Label控件上的TextSearch.Text属性。但是对于未绑定的情况:“TextSearch.Text”属性的“值源”为“Local”。对于绑定的情况:“值源”为“ParentTemplate”。
P.S.(和N.B.) 我知道我可以在列表框上使用TextSearch.TextPath,但这不是我需要的:) 此外,设置ListViewItem的TextSearch.Text属性(使用Style)也无济于事。

2
我已经查看了http://stackoverflow.com/questions/4750220/can-i-do-text-search-with-multibinding。 - denis morozov
2个回答

12

让我先解释一下TextSearch是如何与ItemsControl一起使用的:

TextSearch的实现会枚举ItemsSource属性中的实际数据项,并直接读取那些数据项的Text依赖属性。当您像在示例中那样将ListBoxItem放入其中时,它能够工作是因为实际的项是具有 "附加" Text 依赖属性的ListBoxItem实例。但是,一旦将其绑定到Dictionary<>,它就直接查看KeyValuePair<>实例,这些实例不是DependencyObject,因此不能/没有TextSearch.Text属性。这也是为什么通过ItemContainerStyleListBoxItem上设置TextSearch.Text属性没有任何效果的原因: ItemContainerStyle描述了数据在可视树中的外观,但是引擎只考虑原始数据源。 UI中如何样式化数据并不重要,这就是为什么修改DataTemplate永远不会对TextSearch产生任何影响。

一个替代方案是创建一个继承自DependencyObject的视图模型类,在其中基于您想要进行搜索的值设置TextSearch.Text附加属性。以下是显示如何工作的一些示例代码:

private sealed class MyListBoxItem : DependencyObject
{
    public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key", typeof(string), typeof(MyListBoxItem), new FrameworkPropertyMetadata(string.Empty));
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(MyListBoxItem), new FrameworkPropertyMetadata(string.Empty));

    public string Key
    {
        get
        {
            return (string)GetValue(KeyProperty);
        }
        set
        {
            SetValue(KeyProperty, value);
            SetValue(TextSearch.TextProperty, value);
        }
    }

    public string Value
    {
        get
        {
            return (string)GetValue(ValueProperty);
        }
        set
        {
            SetValue(ValueProperty, value);
        }
    }
}

// Assign a list of these as the list box's ItemsSource
this.listBox.ItemsSource = new List<MyListBoxItem>
{
    new MyListBoxItem { Key = "One", Value = "1" },
    new MyListBoxItem { Key = "Two", Value = "2" },
    new MyListBoxItem { Key = "Three", Value = "3" }
};

ListBox的定义大致如下:

<ListBox Name="listBox">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Value}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

如有其他选择,您需要使用TextSearch.TextPath,但您似乎坚决反对这种方法。如果您认为修改DataTemplate永远不起作用,我建议的解决方案是简单地创建一个Poco视图模型,并在其中添加您想要用于搜索的属性,并将其指定给TextSearch.TextPath。这是最轻量级、非hacky的实现您所做的事情的方法。


感谢您的回复,Drew。但是TextSearch.TextPath不是一个选项(正如我在问题中已经说明的那样)。 - Dima
这个例子是从一个更大的项目中提取出来的。在这个项目中,我将对象绑定到列表上。每个项目的文本通过值转换器生成(该转换器使用对象实例的多个属性)。因此,我想使用同样的转换器来为TextSearch.Text提供值。 - Dima
你理解我关于运行时如何使用 TextSearch.Text 的解释了吗?你简单地不能使用 TextSearch.Text 属性来完成它。这个踩票似乎表明你对此有否认的态度。你不必接受我的答案,但是踩票实在太过苛刻了。我有一种方法可以做到这一点,但你必须放弃转换器的想法。如果你愿意承认 TextSearch.Text 没有办法处理,并且能够对我帮助你付出的时间和精力表示一些欣赏,至少请取消你的踩票,我可以与你分享我的方法。 - Drew Marsh
我已经测试了你的理论,但不幸的是它没有帮助。我创建了一个继承自DependencyObject的类。添加了一个属性:Text(作为字符串)。非常简单的类。创建了一个对象集合。将集合绑定到ListBox控件。将TextSearch.Text设置为“{Binding Text}”。但什么也没发生。如果我使用TextSearch.TextPath,它可以工作。但正如我所说,这不是一个选项。如果你能证明我错了,我会很高兴撤销我的投票。请不要有任何怨恨。我们在这里寻求真相。 - Dima
@Dima,我会解释为什么数据模板在我写的第一个段落末尾的这个原始句子中永远不起作用:“这也是为什么通过ItemContainerStyle在ListBoxItem上设置TextSearch.Text属性没有效果(它只是Chrome而不是数据)。” 我可以理解这可能不是完全清楚的,所以让我来详细说明一下。 - Drew Marsh
显示剩余6条评论

6
您可以尝试使用TextSearch的回退行为,即如果未设置TextSearch.Text或TextSearch.TextPath,则对ListBoxItem的数据项使用.ToString()。例如,这将允许您在不指定TextSearch.Text或.TextPath的情况下进行搜索。
<Page.DataContext>
    <Samples:TextSearchViewModel/>
</Page.DataContext>

<Grid>
    <ListBox ItemsSource="{Binding Items}" 
             IsTextSearchCaseSensitive="False" 
             IsTextSearchEnabled="True">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Label Content="{Binding Value}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

public class TextSearchItem
{
    public int Value { get; set; }
    public string SearchText { get; set; }

    public override string ToString()
    {
        return SearchText;
    }
}

public class TextSearchViewModel
{
    public TextSearchViewModel()
    {
        Items = new List<TextSearchItem>
                    {
                        new TextSearchItem{ Value = 1, SearchText = "One"},
                        new TextSearchItem{ Value = 2, SearchText = "Two"},
                        new TextSearchItem{ Value = 3, SearchText = "Three"},
                        new TextSearchItem{ Value = 4, SearchText = "Four"},
                    };
    }

    public IEnumerable<TextSearchItem> Items { get; set; }
}

非常好!我喜欢有创意的答案。 - denis morozov
太棒了!非常出色的超越传统思维的例子。我从未想过从这个角度来解决问题。我可以确认它是有效的。 - MoonBoots89

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接