Partilhar via


Interacting with templated controls in a Silverlight based Windows Phone 7 app

I have been struggling all day with trying to figure out how to refer to a templated control in a Windows Phone 7 app. The solution I ended up with was nothing like the solution I spent all day trying to figure out. I wanted to write this up for my own benefit so I might remember this experience.

I have a listbox with the following ItemTemplate

         <ListBox x:Name="QuestionListBox" Grid.Row="1"  ItemsSource="{Binding Questions}">

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <StackPanel Margin="15,0,15,17" Width="432">

                        <TextBlock Text="{Binding Question}" TextWrapping="Wrap" />

                        <ListBox x:Name="OptionsItem" SelectionChanged="ListBox_SelectionChanged" ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedCollectionItem, Mode=TwoWay}" ItemContainerStyle="{StaticResource RadioButtonListItemStyleCorrectAnswer}">

                        </ListBox>

                        <TextBlock x:Name="ExplanationTextBlock" Visibility="Collapsed" Text="{Binding Explanation}" TextWrapping="Wrap" />

                    </StackPanel>

                </DataTemplate>

            </ListBox.ItemTemplate>

        </ListBox>
 

It shows a series of multiple choice questions and answers. A TextBlock displays the question, then another listbox shows a series of radio buttons (styled listboxitems) to display each possible answer. A collapsed TextBlock contains the explanation for the correct answer. I wanted to reveal the explanation only when the user had chosen one of the answer options (ie, clicked on one of the radio buttons) so I set the Visibility property of this control to Collapsed by default. I had defined a ViewModel for the list of questions so I was binding the content for each of the UI elements to properties of the question ViewModel.

In order to reveal the explanation whenever the user clicked on a radio button I hooked up an event handler on the click event. In the event handler, I could easily access the specific radio button that was clicked, but I couldn't find a way to access the associated explanation TextBlock. The problem seemed to be that since they are templated controls, there was no easy way to access them in the code behind. I searched online for ways to refer to templated controls and came across lots of samples that showed how to get the corresponding ListBoxItem. I thought then I would be able to get the specific TextBlock using the DataTemplate.FindName method, similar to this MSDN page or this  blog post.

But I found that DataTemplate.FindName and ItemCollection.CurrentItem aren't available when developing on the Windows Phone 7. So that set me off looking for alternatives. I found quite a few examples: handling the Loaded event for each control, walking the visual tree, or deriving from ListBoxItem and overriding OnApplyTemplate.

These all looked interesting, but there was something about them that just didn't feel right. Walking the visual tree for example seemed to me like that might end up with brittle code that would break as soon as the data template changed. Deriving from ListBoxItem seemed quite a heavyweight solution. Surely there was an easier way.

I ended up spending a lot of time refining my search queries, over time going from the incredibly specific ("accessing templated controls silverlight Windows Phone 7") to the far less specific ("databinding templates"). At some point, I came across something that described ValueConverters. Up until that point I hadn't considered my problem as one of converting a value of one type into a value of another. So I never thought of searching for something like this. But when I read about the ValueConverter that sparked another thought process.

Ten minutes later I had accomplished the task. I defined an enum to record the 'answered state' of each question, then added another property to the question ViewModel to record whether or not the question had been answered. Then I bound the Visibility property of the explanation TextBlock to this new property and used a ValueConverter to convert from the enum to the Visibility property of the control.

     public enum QuestionAnswer

    {

        Unanswered,

        Correct,

        Wrong,

    }
 <TextBlock x:Name="ExplanationTextBlock" Visibility="{Binding QuestionAnswered, Mode=TwoWay, Converter={StaticResource visibilityConverter}}" Text="{Binding Explanation}" TextWrapping="Wrap" />
 
     public class VisibilityConverter : IValueConverter

    {

        #region IValueConverter Members



        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            if (parameter == null)

            {

                QuestionAnswer qa = (QuestionAnswer)value;

                return (qa == QuestionAnswer.Unanswered) ? Visibility.Collapsed: Visibility.Visible;

            }

            return false;

        }



        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            throw new NotImplementedException();

        }



        #endregion

    }

It seemed a lot simpler and a lot cleaner to me. Of course, it's still entirely possible that I have completely overlooked a much simpler way to do this but after a day of trying to figure it out, this feels good enough to me.