views:

5936

answers:

5

In Silverlight 2 I'm using a usercontrol which inherits the datacontext of the page that it's embedded on. This datacontext contains question text, a question type and a collection of answers. In the user control is a listbox which is bound to the collection of answers. As shown below:

<ListBox DataContext="{Binding}" x:Name="AnswerListBox" ItemContainerStyle="{StaticResource QuestionStyle}" Grid.Row="1" Width="Auto" Grid.Column="2" ItemsSource="{Binding Path=AnswerList}" BorderBrush="{x:Null}" />

This listbox has an associated style to display the answers in the form of radio buttons or checkboxes (which I would like to hide or show depending on the question type) as:

<Style TargetType="ListBoxItem" x:Key="QuestionStyle">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">                      
                        <StackPanel Background="Transparent" >
                            <RadioButton Visibility="{Binding Path=QuestionType, Converter={StaticResource QuestionTypeConverter}, ConverterParameter='RadioButtonStyle'}" Height="auto" Margin="0,0,0,10"  IsChecked="{TemplateBinding IsSelected}" IsHitTestVisible="False" Content="{Binding Path=AnswerText}">
                            </RadioButton>
                            <CheckBox Visibility="{Binding Path=QuestionType, Converter={StaticResource QuestionTypeConverter}, ConverterParameter='CheckBoxStyle'}" Height="auto" Margin="0,0,0,10" Content="{Binding Path=AnswerText}">
                            </CheckBox>
                        </StackPanel>                                                
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

So my question is then: how do you access the parent data context in order to get the QuestionType (as this is a property on the user control datacontext itself, not a property on an AnswerItem in the AnswerList)?

Alternatively is there a better way to switch styles dynamically in the xaml based on a binding value?

+1  A: 

I've just been battling with a similar problem. Mine consists of a combobox that displays for each listitem. The top-level DataContext is bound to my viewmodel (MVVM) and looks like this:

class ViewModel{
    ObservableCollection<ComboboxListItemType> DataForTheComboBoxList;
    ObservableCollection<MyDataType> DataForTheListBox;
    ...
}

Because the combobox is within the ItemTemplate (=DataTemplate) for the listbox, the DataContext for each list item is set to the appropriate item in DataForTheListBox, the combobox can no longer see the DataForTheComboBoxList it needs from the top-level DataContext.

My (dirty, ugly) workaround involves setting the complete combobox list on each item in the list, so it becomes visible to the DataContext in this list item.

First you make a partial class for your listbox data type. (Generally this will be coming from a service reference, so you cant touch the generated code directly without potentially losing it). This partial class contains a new property referring to your combobox list item type:

public partial class MyDataType
{
    private ObservableCollection<ComboboxListItemType> m_AllComboboxItems;
    public ObservableCollection<ComboboxListItemType> AllComboboxItems
    {
        get { return m_AllComboboxItems; }
        set
        {
            if (m_AllComboboxItems != value)
            {
                m_AllComboboxItems = value;
                RaisePropertyChanged("AllComboboxItems");
            }
        }
    }
}

Next you have to set this property on each element in the DataForTheListBox collection

// in ViewModel class
foreach(var x in this.DataForTheListBox)
{
    x.AllComboboxItems = this.DataForTheComboBoxList;
}

Then back in your XAML:

<DataTemplate x:Key="ListBoxItemTemplate">
    ...
    <Combobox
        ItemsSource="{Binding AllComboboxItems}"
        SelectedItem="{Binding CurrentBlah}"/>
</DataTemplate>

Dont forget that for the combobox to correctly display the current item, the selected item must refer to the actual item in the combobox's ItemsSource. If you are getting data from a webservice that has IDs or objects to represent the item for the combobox, you must re-reference them to point to the actual collection.

geofftnz
+1  A: 

On of the very cool things you can do with silverlight is to use the Tag attribute to store a reference to the object to which it is bound.

First, in your class, declare a property like this

public IMyObject Current { get {return this;} }

Then on the control raising the event in you can get a reference to the object

var fe= (FrameworkElement) sender; var src = fe.Tag as IMyObject;

So now that I have the object, it's reasonable for an object to have reference to it's parent so then you bind to

Current.Parent.QuestionType

A: 

Another, just as ugly method is using RelativeSource in your binding, to walk back up the datacontext heirachy to get to the UserControl's datacontext.

Something like this if you were trying to bind to the "ReportGroup" property of your parent UserControl's data context -

 <ListBox ItemsSource="{Binding Reports}">
       <ListBox.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Path=DataContext.ReportGroup, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Is looks messy, but it works.

If anyone can improve on this idea please let us know.

Wilson
As far as i know does silverlight only support Self an TemplatedParent as RelativeSource for binding
Jehof
+13  A: 

In Silverlight 3 and higher you can use Element to Element binding and point to the controls DataContext and then some property in my example its Threshold property.

So first name your control (for example in my example its x:Name="control")

<UserControl x:Class="SomeApp.Views.MainPageView" x:Name="control" >

then inside this control in your ListBox ItemTemplate you can access parent DataContext like this:

    <ListBox ItemsSource="{Binding Path=SomeItems}" >
        <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding ElementName=control, Path=DataContext.Treshold}"/>
            </StackPanel>
        </DataTemplate>
       </ListBox.ItemTemplate>         
       </ListBox>
Roboblob
important not to miss that you need to explicitly put in 'DataContext'. i had previously abandoned an attempt to do this because i managed to forget to put 'DataContext'
Simon_Weaver
@roboblob - do you happen to know a good way to do this within a DataForm (doesn't work in same way as shown in my post below)
Simon_Weaver
A: 

If you're trying to do this with a DataForm he same method shown by Roboblob doesn't work.

This question may have some useful information

Simon_Weaver