Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of TextSearch.Text in DataTemplate

I have a very simple example: WPF form application with single form which contains a dictionary with data:

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

On form I have a ListBox (named "lst1"), which uses "dict" as items source:

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

Also I have one non-bound ListBox, prefilled with values manually:

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

So when I launch app, it looks like this:

Application's window

THE QUESTION:

If I try to navigate items with keyboard by typing "one", "two" or "three", I succeed only in non-bound list box. Bound list box fails.

Some remarks: 1.) If I press "[" while in bound list box, focus changes from item to item in cyclic manner: it goes from 1 to 2, from 2 to 3, from 3 to 1, from 1 again to 2 etc. 2.) I have checked app with Snoop. One difference I found between bound and non-bound list boxes. Both list boxes have TextSearch.Text property set on Label controls (inside ItemsPresenter). But for non-bound case: "value source" of TextSearch.Text property is "Local". For bound case: "value source" is "ParentTemplate".

P.S. (and N.B.) I know that I can use TextSearch.TextPath on the list box, but this is not what I need :) Also, setting TextSearch.Text property for ListViewItem (by using Style) does not help.

like image 408
Dima Avatar asked Apr 13 '12 21:04

Dima


2 Answers

Let me start by explaining exactly how TextSearch works with the ItemsControl:

TextSearch's implementation enumerates the actual data items of the ItemsSource property and looks at those directly to read the Text dependency property. When you put ListBoxItems in it like you do in your example it works because the actual items are ListBoxItem instances with the Text dependency property "attached" to them. Once you bind to your Dictionary<> it's now looking directly at KeyValuePair<> instances which are not DependencyObjects and thus can't/don't have the TextSearch.Text property on them. This is also why setting the TextSearch.Text property on the ListBoxItem via the ItemContainerStyle has no effect: the ItemContainerStyle is describing how your data should look in the visual tree, but the TextSearch engine only considers the raw data source. It does not matter how you've styled that data in the UI and that is why modifying a DataTemplate will never do anything for TextSearch.

One alternative is creating a view model class that inherits from DependencyObject where you set the TextSearch.Text attached property on that based on the value you want to be searchable. Here's some sample code that shows how this would work:

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 definition looks something like this:

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

Any other alternative will require you to use TextSearch.TextPath, but you seem dead set against that. If you come to accept that modifying the DataTemplate will never work, the solution I would recommend is to simply create a POCO view-model that has the property on it that you want to use for searching and specify that to TextSearch.TextPath. It is the lightest weight, non-hacky way to accomplish what you're doing.

like image 187
Drew Marsh Avatar answered Sep 29 '22 10:09

Drew Marsh


A possible solution for you is to use TextSearch's fallback behaviour of using .ToString() on the data item of the ListBoxItem if no TextSearch.Text or TextSearch.TextPath are set.

So for example, this will allow you to search without specifying TextSearch.Text or .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; }
}
like image 37
Phil Avatar answered Sep 29 '22 10:09

Phil