Usage of TextSearch.Text in DataTemplate
Asked Answered
M

2

7

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.

Merca answered 13/4, 2012 at 21:44 Comment(1)
have looked at #4750720Zaibatsu
C
12

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.

Ciscaucasia answered 16/4, 2012 at 4:47 Comment(11)
Thanks for your reply, Drew. However TextSearch.TextPath is not an option (as I already have stated in the question).Merca
This example is an extraction from much larger project. In the project I bind objects to the list. Text of each item is generated by means of value converter (which uses several properties of object instance). So I want to use same converter to provide values to TextSearch.Text.Merca
You understand my explanation of how the runtime works with TextSearch.Text right? You simply cannot do it with the TextSearch.Text property. The downvote would seem to be an indicator that you're in denial over it. You don't have to accept my answer, but the downvote is rather harsh. I have a way to do it, but you'd have to give up on the converter idea. I can share it with you if you're willing to accept that there's no way TextSearch.Text is going to work and are going to show a little appreciation for my time and effort in helping you by, at bare minimum, removing your downvote.Ciscaucasia
I really apreciate your answer Drew. Wright now I am doing some investigation on what you have wrote about looking directly at KeyValuePair (I have a habit to check everything ;)). Your statement seems logical, but on the other hand: key-value pairs should be wrapped inside item container, shouldn't they? And the item container is dependency object which can use attached property.Merca
I have tested your theory, but unfortunately it did not help. I have created a class inherited from DependencyObject. Added single property: Text (as String). Very simple class. Created a collection of objects. Bound the collection to ListBox control. Set TextSearch.Text to "{Binding Text}". But nothing happened. Still, if I use TextSearch.TextPath - it works. But as I have said it is not an option. I will gladly remove my downvote if you can prove I am mistaken. No hard feelings please. We are looking for truth here.Merca
It's not a theory, it's the implementation of the method. Break out Reflector or JustDecompile and see for yourself. The test using a DependencyObject you just said doesn't work, absolutely does work because I did it myself before I ever responded. Sounds like you're doing something wrong, so I'll add the working code to my answer so you can see it working.Ciscaucasia
Drew, thanks again. But again, you are missing the point of the question. I understand your example, but you are setting TextSearch.TextProperty in object (in property Set) and then binding the object to the list. Your TextSearch.Text is already set. Prior to binding. You can see I have showed this approach in my question: in listing #3. The question is about the usage of TextSearch.Text in DataTemplate. If you remove "SetValue(TextSearch.TextProperty, value)" code from your code and try to set it in DataTemplate, you will see the problem.Merca
@Merca I know exactly what you're asking for, I've explained in detail exactly why it's not possible due to the implementation of TextSearch in WPF and I've now given you a solution to work around it. I hope you won't too much more sleep over the issue, accept the implementation for what it is and eventually embrace the solution I have provided so that you can deliver your product. Good luck.Ciscaucasia
Well, Drew, I do not know how to explain the question to you. I wrote in my question that I do not need TextSearch.TextPath, but you have ignored this requirement. If you do not have an answer for the exact question, please do not post your answer, since it does not answers the question. You state that TextSearch.Text can not be used on non-dependency objects, but you totally ignore DataTemplate usage requirement in question. Next thing: you provide sample code which does not solve the problem: it requires me to modify the data object (modify property Key in your example).Merca
@Merca I explain why the data template will never work in this original sentence at the end of the first paragraph I ever wrote: "This is also why setting the TextSearch.Text property on the ListBoxItem via the ItemContainerStyle has no effect (it's just chrome, not data)." I can see how that might not be completely clear, so let me go expand on it.Ciscaucasia
Drew, you could state view model approach straight ahead. I really hoped you had another silver bullet, since you've been hiding this approach for so long.Merca
D
6

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; }
}
Dislocation answered 18/4, 2012 at 11:33 Comment(2)
very nice! I like creative answers.Zaibatsu
Awesome! Great example of out the box thinking. I had no idea that there was a solution available from this angle. I can confirm that it works.Muscid

© 2022 - 2024 — McMap. All rights reserved.