开发者

Silverlight relativebinding ItemTemplate ListboxItem - Listbox

I created programatically a class (I called it ViewGrid) so that I use an instance of it as ItemTe开发者_C百科mplate for my ListBox control; of course, it's my data template for the listboxitem....

Also, in my ViewGrid class, I got a dependency property called IsChecked and I want to keep it in sync with the ListBoxItem's IsSelected property. I noticed that in SL there no relativesource-findancestor-ancestortype support for binding as in WPF, still, I need to find a way to keep my IsChecked property synchronized with the IsSelected property of the internally generated ListBoxItem for my ListBox control. Can you help?


Here is a ListBox defined in XAML that uses the IsSelected property of each LitBoxItem to show or hide a button when selected. You just need to duplicate that Binding approach for the ListBoxItems you create in code. Either that, or create a UserControl with the appropriate ListBoxItem XAML, and insert instances of those UserControls into your ListBox.

<ListBox>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Width="200" Height="120">
                <StackPanel Margin="5">
                    <TextBlock Text="{Binding Name, Mode=OneWay}" />
                    <StackPanel Visibility="{Binding IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisible}}">
                        <Button Content="Show Details" Click="OnDetailsClick" Tag="{Binding}" />
                    </StackPanel>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Good luck,

Jim McCurdy

Face To Face Software and YinYangMoney


UPDATE: I revisited this and found a much better solution. My original one remains below, but the way I actually ended up solving this problem is via using the ViewGrid in a ControlTemplate instead of a DataTemplate. Then you can use the RelativeSource TemplatedParent binding to bind to the IsSelected property of the ListBox. So, add the following to the Resources of the listbox or your page or user control:

<Style TargetType="ListBoxItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
                <StackPanel>
                    <ViewGrid IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>​
                    <!-- other controls may go here -->
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

ORIGINAL:

So after seven years, you almost certainly don't need an answer to this anymore... however, I recently spent a morning wrestling with this issue and thought I'd give my solution in case any similar unfortunate ends up here.

First off, anyone who's using Silverlight 5 is in luck as AncestorType is apparently now available for RelativeSource, letting you bind directly to the IsSelected property of the ListBoxItem. For those of us stuck with 4 or below, the only real workaround I came up with was "faking" the binding via use of events in the code behind.

To do this, assume you have your YourView XAML with a ListBox named "lbYourListBox" which has its ItemsSource and SelectedItem properties bound to appropriate properties on a YourViewModel class, along with a ViewGrid in its ItemTemplate whose IsChecked property is not bound to anything. Then, in your code behind file, you wire up events as follows:

public YourView()
    {
        InitializeComponent();
        this.Loaded += (sender, e) =>
        {
            ((YourViewModel)this.DataContext).PropertyChanged += vm_PropertyChanged;
            UpdateViewGrids();
        };

    }

    // this part propagates changes from the view to the view model
    private void viewGrid_Checked(object sender, RoutedEventArgs e)
    {
        var selectedVM = ((ViewGrid)sender).DataContext as SourceItemType;
        ((YourViewModel)this.DataContext).SelectedViewGridItem = selectedVM;
    }

    private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (string.Equals(e.PropertyName, "SelectedViewGridItem"))
        {
            UpdateViewGrids();
        }
    }

    // this part propagates changes from the view model to the view
    private void UpdateViewGrids()
    {
        var viewGrids = this.lbYourListBox.GetVisualDescendants().OfType<ViewGrid>();
        var selectedVM = ((YourViewModel)this.DataContext).SelectedViewGridItem;
        foreach (var grid in viewGrids)
        {
            grid.IsChecked = selectedVM == grid.DataContext;
        }
    }​

The viewGrid_Checked event handler should be wired up to the Checked event of the view grid in the ItemTemplate. The GetVisualDescendants() method comes from the Silverlight Toolkit.

Important caveats:

  • The ViewGrid.Checked event should not fire except for the unchecked->checked transition, and no more than one view grid should be able to be selected at once. If those two things aren't true, you'll have to make appropriate edits to ensure this code can't cause an infinite event-driven loop. (Of course, if you don't need two-way binding, you only need one of these event handlers and event ping-pong isn't a concern.)
  • I wrote this for a user control which had its data context set in XAML, which is why the event handler for the view model's PropertyChanged event is only assigned after the view is loaded. Depending on how and when your view and view model are bound to each other, you may have to assign that earlier/later/differently.
  • This won't work if the view grids aren't visible, GetVisualDescendants seems to ignore hidden/collapsed controls.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜