Binding a XamDataGrid Field Property

I recently needed to bind the Visibility of a Field in a XamDataGrid to a property on my ViewModel.  I wanted to provide a way for the user to show/hide a column of images in the data grid.  My ViewModel object is a POCO (Plain Old Clr Object) that implements INotifyPropertyChanged, and represents the data and UI state of my Window.  Unfortunately a Field object is not in the element tree and does not derive from Freezable, which means it does not have an inheritance context, thus its properties cannot participate in normal WPF data binding. 

If/when WPF eventually provides a way to give any object an inheritance context, this won't be an issue, but for now there is no clean way to bind a Field's properties to objects inherited down the logical tree, or to the properties of other elements.  This blog post shows a workaround that I came up with, allowing me to easily bind the Visibility of a Field to any object.

Here is the basic idea of what I'm trying to accomplish.  First the grid displays a column of images:

 

If you then click the "Show Photos" CheckBox in the ToolBar, the column of photos is hidden, as seen below:

 

 Here is the code-behind of the Window:

public partial class Window1 : Window
{
    public Window1()
    {
        Person[] people = new Person[]
        {
            new Person("Boss Hogg", 42, "hogg.jpg"),
            new Person("Johann Bach", 50, "bach.jpg"),
            new Person("Mugatu", 39, "mugatu.gif"),
            new Person("Simon Wolcott", 24, "wolcott.jpg")
        };

        base.DataContext = new CommunityViewModel(people);
        Application.Current.Resources.Add("DATA_CommunityViewModel", base.DataContext);

        InitializeComponent();
    }
}

Notice that I'm adding the CommunityViewModel class to both the DataContext of the Window and the Resources of the App.  I set the DataContext to the ViewModel so that the XamDataGrid and CheckBox controls can bind to its properties.  I add it to the App's Resources so that the Field's Visibility binding can access it.  Here is the CommunityViewModel class:

class CommunityViewModel : INotifyPropertyChanged
{
    bool _showPhotos;

    public CommunityViewModel(IList<Person> constituents)
    {
        this.Constituents = new ReadOnlyCollection<Person>(constituents);
        _showPhotos = true;
    }

    public ReadOnlyCollection<Person> Constituents { get; set; }

    public bool ShowPhotos
    {
        get { return _showPhotos; }
        set
        {
            if (value == _showPhotos)
                return;

            _showPhotos = value;

            this.OnPropertyChanged("ShowPhotos");
        }
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

The magic happens in the XAML.  Pay close attention to the 'Photo' Field declaration:

<Window
  x:Class="XamDataGridWithBoundField.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:igDP="http://infragistics.com/DataPresenter"
  Title="Window1"
  Width="400" Height="400"
  >
  <Window.Resources>
    <Style
      x:Key="PhotoCellStyle"
      TargetType="{x:Type igDP:CellValuePresenter}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
            <Image
              Source="{Binding
                RelativeSource={RelativeSource TemplatedParent},
                Path=Content}"
              Width="60" Height="60"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>
  <DockPanel>
    <ToolBar DockPanel.Dock="Top">
      <CheckBox
        Content="Show Photos"
        IsChecked="{Binding Path=ShowPhotos}"
        />

    </ToolBar>

    <igDP:XamDataGrid
      AutoFit="True"
      DataSource="{Binding Constituents}"
      >
      <igDP:XamDataGrid.FieldLayouts>
        <igDP:FieldLayout>
          <igDP:FieldLayout.Fields>
            <igDP:Field Label="Name" Name="Name" />
            <igDP:Field Label="Photo" Name="ImageUri">
              <igDP:Field.Settings>
                <igDP:FieldSettings
                  CellMaxWidth="70" LabelMaxWidth="70"
                  CellValuePresenterStyle="{StaticResource PhotoCellStyle}"
                  />
              </igDP:Field.Settings>
              <igDP:Field.Visibility>
                <Binding
                  Path="ShowPhotos"
                  Source="{StaticResource DATA_CommunityViewModel}"
                  >
                  <Binding.Converter>
                    <BooleanToVisibilityConverter />
                  </Binding.Converter>
                </Binding>
              </igDP:Field.Visibility>
            </igDP:Field>

            <igDP:Field Label="Age" Name="Age" />
          </igDP:FieldLayout.Fields>
        </igDP:FieldLayout>
      </igDP:XamDataGrid.FieldLayouts>
    </igDP:XamDataGrid>
  </DockPanel>
</Window>


You can download the demo source code here.  You will need to have the Infragistics NetAdvantage for WPF installed to run the application.


Comments  (5 )

ainsleybowker
on Wed, Jul 22 2009 11:52 AM

great article, this is something I've been investigating for a while.. While this works well for a single ViewModel instance, what about where you have multiple instances of the ViewModel? How do you know which ViewModel to bind the source of the Visibility to? Also what is the cleanest way to remove the ViewModel from the resources when you dispose of the View?

bobby
on Thu, Feb 11 2010 6:29 PM

I didn't think changing the Binding to use the ElementName or the RelativeSource would fail. Is there any reason why this doesn't work? Is my code incorrect? Please let me know. See my details at forums.infragistics.com/.../219370.aspx

Thanks in advance.

jpearce
on Fri, Aug 6 2010 9:01 AM

I'm trying to do something similar at the moment (binding the AllowEdit property to a boolean on the view model), however, my view models are injected via Unity DependencyAttributes, so they're not available when the constructor is run. How would this example be modified to cope with that change?

manishkungwani
on Fri, Apr 8 2011 5:52 AM

Hi,

Can the same code be used to bind the text fields to a dataset, i have posted my question here:

news.infragistics.com/.../54180.aspx

dupond
on Thu, Jun 9 2011 4:08 AM

I would like to submit another method.

Define a resource in your XAML view:

<base:RteViewBase.Resources>

       <ObjectDataProvider x:Key="ViewModel" />    </base:RteViewBase.Resources>

Define a Loaded attribute in your xaml view:

<base:RteViewBase

...

Loaded="ValidationReportView_Loaded">

In the code-behind, defined the method for the event Loaded

private void ValidationReportView_Loaded(object sender, RoutedEventArgs e)

       {

           //Associate VM to ObjectDataProvider

           (this.Resources["ViewModel"] as ObjectDataProvider).ObjectInstance = this.DataContext;

       }

Now, in the xamDataGrid you can bind your column attribute to a property of the view model. For instance:

MyAttribute="{Binding Source={StaticResource ViewModel}, Path=ColumnsVisibilityStatus,...}