Binding to a viewmodel property in a DataTemplate
Asked Answered
C

1

13

I'm fairly new to XAML but enjoying learning it. The thing I'm really struggling with is binding a property to an element in a DataTemplate.

I have created a simple WPF example to, (hopefully,) explain my problem.

I this example I am trying to bind the Visibility property of a CheckBox in a DataTemplate to a Property in my viewmodel. (Using this scenario purely for learning/demo.)

I have a simple DataModel named Item, but is of little relevance in this example.

class Item : INotifyPropertyChanged
{

    // Fields...
    private bool _IsRequired;
    private string _ItemName;

And a fairly simple View Model named ItemViewModel.

class ItemViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Item> _Items;
    private bool _IsCheckBoxChecked;
    private bool _IsCheckBoxVisible;

    public ObservableCollection<Item> Items
    {
        get { return _Items; }
        set { _Items = value; }
    }


    public bool IsCheckBoxChecked
    {
        get { return _IsCheckBoxChecked; }
        set
        {
            if (_IsCheckBoxChecked == value)
                return;
            _IsCheckBoxChecked = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxChecked"));
                PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
            }
        }
    }


    public bool IsCheckBoxVisible
    {
        get { return !_IsCheckBoxChecked; }
        set
        {
            if (_IsCheckBoxVisible == value)
                return;
            _IsCheckBoxVisible = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
        }

(Constructors and INotifyPropertyChanged implementation omitted for brevity.)

Controls laid out in MainPage.xaml as follows.

<Window.Resources>
    <local:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>

<Window.DataContext>
    <local:ItemViewModel/>
</Window.DataContext>

<Grid>
    <StackPanel>
        <CheckBox x:Name="checkBox" Content="Hide CheckBoxes"  FontSize="14"  IsChecked="{Binding IsCheckBoxChecked, Mode=TwoWay}" />
        <ListView ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" >
            <ListView.ItemTemplate >
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding ItemName}"/>
                        <CheckBox  Grid.Column="1" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}"   >
                            <CheckBox.DataContext>
                                <local:ItemViewModel/>
                            </CheckBox.DataContext>
                        </CheckBox>
                    </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
       <StackPanel Orientation="Horizontal" Margin="4,4,0,0">
        <TextBlock Text="IsCheckBoxVisible:"/>
            <TextBlock Text="{Binding IsCheckBoxVisible}" Margin="4,0,0,0" FontWeight="Bold" />
        </StackPanel >
        <Button Content="Button" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" Margin="4,4,4,4"/>
    </StackPanel>

</Grid>

The 'Hide CheckBoxes' checkbox is bound to IsCheckBoxChecked and is used to update IsCheckBoxVisible. I've also added a couple of extra controls below the DataTemplate to prove, (to myself,) the everything works.)

I have also implemented Jeff Wilcox's value converter. (Thank you.) http://www.jeff.wilcox.name/2008/07/visibility-type-converter/

When I run the app, checking and unchecking the 'Hide Checkbox', controls outside the DataTemplate function as expected but, alas, the Checkbox inside the data template remains unchanged.

I have had success with:

IsVisible="{Binding IsChecked, Converter={StaticResource VisibilityConverter}, ElementName=checkBox}"

But I'm not just trying mimic another control but make decisions based on a value.

I would REALLY appreciate any help or advice you can offer.

Thank you.

Chavey answered 1/4, 2013 at 17:48 Comment(3)
Do you get any binding errors in the debug output window in Visual Studio? They are usually a good indication of what's going wrong.Fanlight
Chris. Thank for your response. Checked the Output windows and, as you suspected, there were errors. It couldn't 'find' IsCheckBoxVisible. Applied fix as per Duncan's reply below and all good now. Thank you.Chavey
Does this answer your question? Binding to viewmodel from inside a datatemplateNicolas
L
30

When you are in a DataTemplate, your DataContext is the data templated object, in this case an Item. Thus, the DataContext of the CheckBox in the DataTemplate is an Item, not your ItemViewModel. You can see this by your <TextBlock Text="{Binding ItemName}"/>, which binds to a property on the Item class. The Binding to IsCheckBoxVisible is trying to find a property called IsCheckBoxVisible on Item.

There are a couple of ways around this, but by far the easiest is to do this:

On your Window (in the xaml), give it and x:Name. Eg:

<Window [...blah blah...]
        x:Name="MyWindow">

Change your binding to look like this:

<CheckBox Grid.Column="1"
          Visibility="{Binding DataContext.IsCheckBoxVisible, ElementName=MyWindow, Converter={StaticResource VisibilityConverter}}">

We're using the Window as the source for the Binding, then looking at its DataContext property (which should be your ItemViewModel, and then pulling off the IsCheckBoxVisible property.

Another option, if you want something fancier, is to use a proxy object to reference your DataContext. See this article on DataContextProxy.

Lollard answered 2/4, 2013 at 4:13 Comment(2)
Duncan. Thank you for your prompt reply. I've tried your suggestion and it worked like a charm. I thought adding a separate <DataContext> tag for the checkbox would work but alas, no. Once again thank you. Will give Dan Wahlin's atricle a read in the link you provided as well. (I tried voting up your answer but my lowly reputation prevents me from doing so.)Chavey
Glad I could help! :-) It's not completely relevant, but just as an aside, from your comment: manually setting the DataContext of an element (except the Window or another root) is something you want to be very careful with, and is often a sign that you're doing something wrong. I'm imagining your attempt was along the lines of <CheckBox.DataContext><local:ItemViewModel/></...>: if so, the reason it wouldn't work is when you define the ItemViewModel in xaml like that you are creating one, so you'd have a different instance. You can get around this, but the solution I described is better.Lollard

© 2022 - 2024 — McMap. All rights reserved.