Click here to Skip to main content
11,640,599 members (67,512 online)
Click here to Skip to main content

Displaying a Data Matrix in WPF

, 14 Jun 2009 CPOL 141.7K 2.4K 154
Rate this:
Please Sign up or sign in to vote.
Reviews a matrix control that can be data bound and visually styled with ease

MoneyShot.png

Introduction

This article reviews a WPF control, and a set of related classes, that makes it easy to create and display a matrix of data. In addition to seeing how to use the matrix control, we will also explore how it works.

Background

A common way to display a set of related data entities, such that it is easy to compare them against each other, is in a matrix. Like a data grid, a matrix consists of rows and columns. Unlike a data grid, the rows in a matrix also have headers, each of which display an attribute or value related to the entities being displayed. By including row headers, a matrix exposes additional dimensions of data beyond what a standard data grid can display.

Anatomy of a Matrix

Throughout this article, and the associated source code, we reference specific parts of a matrix by name. The following annotated screenshot points out each of the parts.

Anatomy.png

Introducing MatrixControl and MatrixBase

The source code package available at the top of this article contains a Visual Studio 2008 solution with two projects. The MatrixLib project contains the MatrixControl class, which is a UI control that you can use to display a data matrix. MatrixControl derives from ItemsControl and arranges its child elements in a grid layout. You can easily drop a MatrixControl into any user interface and bind its ItemsSource property to a collection of objects that should be displayed as a matrix.

However, there’s more to it than that. Taking a set of data entities and transforming them into a one-dimensional collection of objects that can be bound to and displayed in a two-dimensional grid layout requires a bit of extra logic. You need to somehow let the MatrixControl know which ‘slot’ in the grid each item should be placed. In order to easily apply a visual style to the matrix, you need to somehow differentiate in XAML between column headers, row headers, and cells. Also, you need to figure out a way to get the value that should be displayed in each cell (i.e. the value that corresponds to each row/column intersection). In order to make it easier and faster to accomplish these tasks, I created the MatrixBase<TRow, TColumn> class. All you need to do is derive a class from MatrixBase and override a few methods; all of the heavy lifting will be taken care of for you.

The following two sections of this article demonstrate how to use MatrixControl and MatrixBase.

Demo 1 - Country Matrix

CountryMatrix.png

The WpfMatrixDemo project in this article’s source code package contains two examples of using MatrixControl. In this section we will see how to create a matrix that displays a list of countries and various attributes about those countries.

The data for this demo resides in Country objects. The Country class is seen below:

class Country
{
    public double ExportsInMillions { get; set; }
    public double ExternalDebtInMillions { get; set; }
    public string FlagIcon { get; set; }
    public double GDPInMillions { get; set; }
    public double LifeExpectancy { get; set; }
    public string Name { get; set; }
}

An array of Country objects is created by the Database class:

public static Country[] GetCountries()
{
    return new Country[]
    {
        new Country
        {
            Name = "Switzerland",
            ExportsInMillions = 172700,
            ExternalDebtInMillions = 1340000,
            FlagIcon = "Flags/switzerland.png",
            GDPInMillions = 492595,
            LifeExpectancy = 80.62
        },
        new Country
        {
            Name = "United Kingdom",
            ExportsInMillions = 468700,
            ExternalDebtInMillions = 10450000,
            FlagIcon = "Flags/uk.png",
            GDPInMillions = 2674085,
            LifeExpectancy = 78.7
        },
        new Country
        {
            Name = "United States",
            ExportsInMillions = 1377000,
            ExternalDebtInMillions = 13703567,
            FlagIcon = "Flags/usa.png",
            GDPInMillions = 14264600,
            LifeExpectancy = 78.06
        }
    };
}

The array of Country objects is loaded into CountryMatrix, which is declared below:

/// <summary>
/// A matrix that displays countries in the columns
/// and attributes of a country in the rows.
/// </summary>
class CountryMatrix : MatrixBase<string, Country>
{
    // ...
}

Notice that MatrixBase is a generic class and has two type parameters. The first type parameter, TRow, specifies what type of object will be placed into row headers. The second type parameter, TColumn, indicates the type of object will be placed into column headers.

CountryMatrix has the following initialization code:

public CountryMatrix()
{
    _countries = Database.GetCountries();
    _rowHeaderToValueProviderMap = new Dictionary<string, CellValueProvider>();
    this.PopulateCellValueProviderMap();
}

void PopulateCellValueProviderMap()
{
    // Use the American culture to force currency 
    // formatting to use the dollar sign ($).
    CultureInfo culture = new CultureInfo("en-US");

    _rowHeaderToValueProviderMap.Add(
        "Exports (millions)",
        country => country.ExportsInMillions.ToString("c0", culture));

    _rowHeaderToValueProviderMap.Add(
        "External Debt (millions)",
        country => country.ExternalDebtInMillions.ToString("c0", culture));

    _rowHeaderToValueProviderMap.Add(
        "GDP (millions)",
        country => country.GDPInMillions.ToString("c0", culture));

    _rowHeaderToValueProviderMap.Add(
        "Life Expectancy",
        country => country.LifeExpectancy.ToString("f2"));
}

// Fields
readonly Country[] _countries;
readonly Dictionary<string, CellValueProvider> _rowHeaderToValueProviderMap;

/// <summary>
/// This delegate type describes the signature of a method 
/// used to produce the value of a cell in the matrix.
/// </summary>
private delegate object CellValueProvider(Country country)

The _rowHeaderToValueProviderMap field associates the values shown in row headers with a callback method that is used to produce the value of each cell in that row. That callback receives a Country object as a parameter (which comes from the column header), and returns some value to display in that cell. We can see how this technique is put to use when looking at the overridden methods of CountryMatrix:

protected override IEnumerable<Country> GetColumnHeaderValues()
{
    return _countries;
}

protected override IEnumerable<string> GetRowHeaderValues()
{
    return _rowHeaderToValueProviderMap.Keys;
}

protected override object GetCellValue(
    string rowHeaderValue, Country columnHeaderValue)
{
    return _rowHeaderToValueProviderMap[rowHeaderValue](columnHeaderValue);
}

If you open the AppWindow.xaml file, you will see that an instance of CountryMatrix is set as the DataContext of a MatrixControl, and its MatrixItems property is the source for the control’s ItemsSource property binding.

<mx:MatrixControl ItemsSource="{Binding Path=MatrixItems}">
  <mx:MatrixControl.DataContext>
    <local:CountryMatrix />
  </mx:MatrixControl.DataContext>
  <mx:MatrixControl.Resources>
    <ResourceDictionary Source="CountryMatrixTemplates.xaml" />
  </mx:MatrixControl.Resources>
</mx:MatrixControl>

The MatrixControl declared above is injected with a ResourceDictionary contained in the CountryMatrixTemplates.xaml file. That file contains a DataTemplate for each of the various parts of the matrix. An abridged version of that file is shown below. The only template that remains intact is the one that is used to display column headers. In this demo, each column header contains a flag icon and the name of a country.

<ResourceDictionary 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mx="clr-namespace:MatrixLib.Matrix;assembly=MatrixLib"
  >
  <!-- Shared Resources -->
  <SolidColorBrush x:Key="BackBrush" Color="LightBlue" />
  <SolidColorBrush x:Key="BorderBrush" Color="LightBlue" />
  <Thickness x:Key="BorderThickness" Left="0" Top="0" Right="0.5" Bottom="0.5" />
  <SolidColorBrush x:Key="HeaderForeground" Color="DarkBlue" />

  <DataTemplate DataType="{x:Type mx:MatrixColumnHeaderItem}">
    <Border 
      Background="{StaticResource BackBrush}" 
      BorderBrush="{StaticResource BorderBrush}" 
      BorderThickness="{StaticResource BorderThickness}" 
      Padding="0,4"
      >
      <DockPanel>
        <Image 
          DockPanel.Dock="Left" 
          Margin="3,0,0,0"
          Source="{Binding Path=ColumnHeader.FlagIcon}"  
          Width="18" Height="12" 
          />
        <TextBlock 
          FontWeight="Bold"
          Foreground="{StaticResource HeaderForeground}"
          Text="{Binding Path=ColumnHeader.Name}" 
          TextAlignment="Center"
          />
      </DockPanel>
    </Border>
  </DataTemplate>

  <DataTemplate DataType="{x:Type mx:MatrixEmptyHeaderItem}">
    <!-- ... -->
  </DataTemplate>

  <DataTemplate DataType="{x:Type mx:MatrixRowHeaderItem}">
    <!-- ... -->
  </DataTemplate>

  <DataTemplate DataType="{x:Type mx:MatrixCellItem}">
    <!-- ... -->
  </DataTemplate>
</ResourceDictionary>

Each of the DataTemplates shown above targets a type that represents a certain part of a matrix, such as how the template for MatrixColumnHeaderItem is used to render column headers. All of those types derive from MatrixItemBase, as seen in the following class diagram. This information is useful when creating templates that are used to render instances of these types.

MatrixClassDiagram.png

Later in this article, we will review how these types are created and arranged internally by MatrixControl. For now, just accept that instances of these types will be placed into the MatrixControl automatically by MatrixBase.

Demo 2 - Person Matrix

PersonMatrix.png

In the previous demo, we saw how to display a matrix that contained the names of countries in the column headers, attributes of a country in the row headers, and the value of each attribute for each country in the cells. In this demo, we will create a different kind of matrix. This matrix displays the names of people in the column headers, a unique list of countries in which those people live in the row headers, and a visual indicator in the cells if a person lives in a certain country. Instead of showing various attributes of a person in the row headers, we show a list of unique values for one attribute of a person in the row headers.

Here is the method in the Database class that creates an array of Person objects:

public static Person[] GetPeople()
{
    return new Person[]
    {
        new Person 
        { 
            Name= "Brennon", 
            CountryOfResidence = "United Kingdom"
        },
        new Person
        {
            Name="Josh", 
            CountryOfResidence ="United States"
        },
        new Person
        {
            Name="Karl", 
            CountryOfResidence= "United States"
        },
        new Person
        {
            Name="Laurent", 
            CountryOfResidence="Switzerland"
        },
        new Person
        {
            Name="Sacha", 
            CountryOfResidence= "United Kingdom"
        }      
    };
}

In this demo, the PersonMatrix class derives from MatrixBase. That class is listed below in its entirety:

/// <summary>
/// A matrix that displays people in the columns 
/// and countries in which people live in the rows.
/// </summary>
public class PersonMatrix : MatrixBase<string, Person>
{
    public PersonMatrix()
    {
        _people = Database.GetPeople();
    }

    protected override IEnumerable<Person> GetColumnHeaderValues()
    {
        return _people;
    }

    protected override IEnumerable<string> GetRowHeaderValues()
    {
        // Return a sorted list of unique country names.
        return
            from person in _people
            orderby person.CountryOfResidence
            group person by person.CountryOfResidence into countryGroup
            select countryGroup.Key;
    }

    protected override object GetCellValue(
        string rowHeaderValue, Person columnHeaderValue)
    {
        return rowHeaderValue == columnHeaderValue.CountryOfResidence;
    }

    readonly Person[] _people;
}

Since this matrix does not require the value of multiple attributes for each data entity, there is no need for the row-to-cell value mapping technique used in the first demo. The GetCellValue method simply returns true if the specified person lives in the specified country, or false if that’s not the case.

Here is the markup in AppWindow.xaml that configures a MatrixControl to display a PersonMatrix instance:

<mx:MatrixControl ItemsSource="{Binding Path=MatrixItems}">
  <mx:MatrixControl.DataContext>
    <local:PersonMatrix />
  </mx:MatrixControl.DataContext>
  <mx:MatrixControl.Resources>
    <ResourceDictionary Source="PersonMatrixTemplates.xaml" />
  </mx:MatrixControl.Resources>
</mx:MatrixControl>

The PersonMatrixTemplates.xaml file contains DataTemplates used to visually style this matrix. One point of interest in that file is the template which renders each matrix cell. It uses a DataTrigger to hide the visual indicator if a person does not live in the country associated with the row in which the cell resides.

<DataTemplate DataType="{x:Type mx:MatrixCellItem}">
  <Border 
    x:Name="bd" 
    Background="#110000FF" 
    BorderBrush="{StaticResource BorderBrush}" 
    BorderThickness="{StaticResource BorderThickness}" 
    >
    <Ellipse 
      x:Name="ell"
      Fill="DarkBlue"
      HorizontalAlignment="Center"
      Width="16" Height="16"  
      VerticalAlignment="Center" 
      />
  </Border>
  <DataTemplate.Triggers>
    <DataTrigger Binding="{Binding Path=Value}" Value="False">
      <Setter TargetName="ell" Property="Visibility" Value="Collapsed" />
      <Setter TargetName="bd" Property="Background" Value="White" />
    </DataTrigger>
  </DataTemplate.Triggers>
</DataTemplate>

At this point we have reviewed two examples of how to use MatrixControl and MatrixBase. The remainder of this article explores how those classes work.

How MatrixControl Works

The MatrixControl class is quite simple. It is merely an ItemsControl subclass with a custom ItemsPanel and ItemContainerStyle. Its code-behind file is empty, apart from the standard boilerplate code that calls InitializeComponent in the constructor. The following XAML is all there is to MatrixControl:

<ItemsControl 
  x:Class="MatrixLib.Matrix.MatrixControl"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:layout="clr-namespace:MatrixLib.Layout"
  >
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <layout:MatrixGrid />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <ItemsControl.ItemContainerStyle>
    <!-- 
    Bind each ContentPresenter's attached Grid 
    properties to MatrixItemBase properties. 
    -->
    <Style TargetType="{x:Type ContentPresenter}">
      <Setter Property="Grid.Row" Value="{Binding Path=GridRow}" />
      <Setter Property="Grid.Column" Value="{Binding Path=GridColumn}" />
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>

The real logic behind this control lives in the MatrixGrid layout panel, as well as MatrixBase and an assortment of MatrixItemBase subclasses. Let’s see how those work next.

How MatrixGrid Works

As mentioned above, MatrixControl is an ItemsControl subclass that uses a MatrixGrid as its items panel. MatrixGrid derives from the standard Grid panel, and has the ability to add the appropriate number of rows and columns to itself in order to properly host its child elements. As of this writing, MatrixGrid does not remove rows or columns from itself when its visual children are removed or moved to a different row or column, simply because none of my use cases required that functionality.

MatrixGrid monitors its visual children (i.e. its “child elements”). It establishes bindings on its child elements, specifically to the Grid.Row and Grid.Column attached properties on its children. When it detects a new value for either attached property on a child element, it will, if necessary, add more rows or columns to itself, in order to allow that child element to be positioned in its desired location. The data binding source is the child element, and the target of the binding is a MatrixGridChildMonitor, which is defined as:

/// <summary>
/// Exposes two dependency properties which are bound to in
/// order to know when the visual children of a MatrixGrid are
/// given new values for the Grid.Row and Grid.Column properties.
/// </summary>
class MatrixGridChildMonitor : DependencyObject
{
    public int GridRow
    {
        get { return (int)GetValue(GridRowProperty); }
        set { SetValue(GridRowProperty, value); }
    }

    public static readonly DependencyProperty GridRowProperty =
        DependencyProperty.Register(
        "GridRow",
        typeof(int),
        typeof(MatrixGridChildMonitor),
        new UIPropertyMetadata(0));

    public int GridColumn
    {
        get { return (int)GetValue(GridColumnProperty); }
        set { SetValue(GridColumnProperty, value); }
    }

    public static readonly DependencyProperty GridColumnProperty =
        DependencyProperty.Register(
        "GridColumn",
        typeof(int),
        typeof(MatrixGridChildMonitor),
        new UIPropertyMetadata(0));
}

Each child element is bound to one of these monitor objects when it is added to a MatrixGrid. Here is the code in MatrixGrid that establishes the binding:

protected override void OnVisualChildrenChanged(
    DependencyObject visualAdded, DependencyObject visualRemoved)
{
    base.OnVisualChildrenChanged(visualAdded, visualRemoved);

    if (visualAdded != null)
        this.StartMonitoringChildElement(visualAdded);
    else
        this.StopMonitoringChildElement(visualRemoved);
}

void StartMonitoringChildElement(DependencyObject childElement)
{
    // Create a MatrixGridChildMonitor in order to detect
    // changes made to the Grid.Row and Grid.Column attached 
    // properties on the new child element.

    MatrixGridChildMonitor monitor = new MatrixGridChildMonitor();

    BindingOperations.SetBinding(
        monitor,
        MatrixGridChildMonitor.GridRowProperty,
        this.CreateMonitorBinding(childElement, Grid.RowProperty));

    BindingOperations.SetBinding(
        monitor,
        MatrixGridChildMonitor.GridColumnProperty,
        this.CreateMonitorBinding(childElement, Grid.ColumnProperty));

    _childToMonitorMap.Add(childElement, monitor);
}

Binding CreateMonitorBinding(DependencyObject childElement, DependencyProperty property)
{
    return new Binding
    {
        Converter = _converter,
        ConverterParameter = property,
        Mode = BindingMode.OneWay,
        Path = new PropertyPath(property),
        Source = childElement
    };
}

Dictionary<DependencyObject, MatrixGridChildMonitor> _childToMonitorMap;
MatrixGridChildConverter _converter;

You might be wondering how merely binding to the value of Grid.Row and Grid.Child on elements would allow MatrixGrid to know how many rows and columns it should create for itself. The answer lies in the use of a value converter, called MatrixGridChildConverter. That converter intercepts the value transferred from the child element to its associated MatrixGridChildMonitor, and lets the MatrixGrid know about the new value. Here is the Convert method in that value converter:

public object Convert(
    object value, Type targetType, 
    object parameter, CultureInfo culture)
{
    if (value is int)
    {
        int index = (int)value;
        if (parameter == Grid.RowProperty)
            _matrixGrid.InspectRowIndex(index);
        else
            _matrixGrid.InspectColumnIndex(index);
    }
    return value;
}

When the MatrixGrid is informed of a new row or column index on one of its child elements, via the InspectRowIndex or InspectColumnIndex methods, it adds the appropriate number of rows/columns to itself. Here is the method in MatrixGrid that adds the correct number of rows:

internal void InspectRowIndex(int index)
{
    // Delay the call that adds rows in case the RowDefinitions 
    // collection is currently read-only due to a layout pass.
    base.Dispatcher.BeginInvoke(new Action(delegate
        {
            while (base.RowDefinitions.Count - 1 < index)
            {
                base.RowDefinitions.Add(new RowDefinition());

                // Make the column headers just tall 
                // enough to display their content.
                if (base.RowDefinitions.Count == 1)
                    base.RowDefinitions[0].Height = 
                        new GridLength(1, GridUnitType.Auto);
            }
        }));
}

The InspectColumnIndex method is very similar to the one seen above.

How MatrixBase Works

MatrixBaseClassDiagram.png

The last piece of this puzzle is the MatrixBase class. As seen previously, you can derive from this class, override a few methods, and then use an instance of that class as the data source for MatrixControl. The ItemsSource property of MatrixControl should be bound to the MatrixItems property of MatrixBase, which is defined as:

/// <summary>
/// Returns a read-only collection of all cells in the matrix.
/// </summary>
public ReadOnlyCollection<MatrixItemBase> MatrixItems
{
    get
    {
        if (_matrixItems == null)
        {
            _matrixItems = new ReadOnlyCollection<MatrixItemBase>(this.BuildMatrix());
        }
        return _matrixItems;
    }
}

When the BuildMatrix method executes, the child class’s overridden methods are invoked to retrieve a list of the column headers and a list of the row headers. MatrixBase then starts constructing MatrixItemBase-derived objects and injecting them with whatever objects were returned by the child class’s overridden methods. When each MatrixCellItem is created, the child class is asked to provide a value for that cell. The complete algorithm from MatrixBase is listed below:

List<MatrixItemBase> BuildMatrix()
{
    List<MatrixItemBase> matrixItems = new List<MatrixItemBase>();

    // Get the column and row header values from the child class.
    List<TColumn> columnHeaderValues = this.GetColumnHeaderValues().ToList();
    List<TRow> rowHeaderValues = this.GetRowHeaderValues().ToList();

    this.CreateEmptyHeader(matrixItems);
    this.CreateColumnHeaders(matrixItems, columnHeaderValues);
    this.CreateRowHeaders(matrixItems, rowHeaderValues);
    this.CreateCells(matrixItems, rowHeaderValues, columnHeaderValues);

    return matrixItems;
}

void CreateEmptyHeader(List<MatrixItemBase> matrixItems)
{
    // Insert a blank item in the top left corner.
    matrixItems.Add(new MatrixEmptyHeaderItem
    {
        GridRow = 0,
        GridColumn = 0
    });
}

void CreateColumnHeaders(
    List<MatrixItemBase> matrixItems, List<TColumn> columnHeaderValues)
{
    // Insert the column header items in the first row.
    for (int column = 1; column <= columnHeaderValues.Count; ++column)
    {
        matrixItems.Add(new MatrixColumnHeaderItem(columnHeaderValues[column - 1])
        {
            GridRow = 0,
            GridColumn = column
        });
    }
}

void CreateRowHeaders(
    List<MatrixItemBase> matrixItems, List<TRow> rowHeaderValues)
{
    // Insert the row headers items in the first slot 
    // of each row after the column header row.
    for (int row = 1; row <= rowHeaderValues.Count; ++row)
    {
        matrixItems.Add(new MatrixRowHeaderItem(rowHeaderValues[row - 1])
        {
            GridRow = row,
            GridColumn = 0
        });
    }
}

void CreateCells(
    List<MatrixItemBase> matrixItems, 
    List<TRow> rowHeaderValues, 
    List<TColumn> columnHeaderValues)
{
    // Insert a cell item for each row/column intersection.
    for (int row = 1; row <= rowHeaderValues.Count; ++row)
    {
        TRow rowHeaderValue = rowHeaderValues[row - 1];

        for (int column = 1; column <= columnHeaderValues.Count; ++column)
        {
            // Ask the child class for the cell's value.
            object cellValue = this.GetCellValue(
                rowHeaderValue, 
                columnHeaderValues[column - 1]);

            matrixItems.Add(new MatrixCellItem(cellValue)
            {
                GridRow = row,
                GridColumn = column
            });
        }
    }
} 

Notice how each MatrixItemBase-derived object that is created in the code above is assigned values for its GridRow and GridColumn properties. Those properties are bound to by MatrixControl’s ItemContainerStyle, so that the ContentPresenter which hosts the MatrixItemBase object will be assigned the correct Grid.Row and Grid.Column attached property values. The XAML from MatrixControl that accomplishes this binding is seen below:

<ItemsControl.ItemContainerStyle>
  <!-- 
  Bind each ContentPresenter's attached Grid 
  properties to MatrixItemBase properties. 
  -->
  <Style TargetType="{x:Type ContentPresenter}">
    <Setter Property="Grid.Row" Value="{Binding Path=GridRow}" />
    <Setter Property="Grid.Column" Value="{Binding Path=GridColumn}" />
  </Style>
</ItemsControl.ItemContainerStyle>

Revision History

  • June 14, 2009 – Published the article to CodeProject

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.

He works at Cynergy Systems as a Senior Experience Developer.

Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.

Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.

Check out his Advanced MVVM[^] book.

Visit his WPF blog[^] or stop by his iOS blog[^].

You may also be interested in...

Comments and Discussions

 
QuestionRow/Column Removal Pin
jrussel931-Dec-14 8:09
memberjrussel931-Dec-14 8:09 
QuestionInteraction with the Matrix Pin
Member 473896525-Mar-14 13:07
memberMember 473896525-Mar-14 13:07 
QuestionNeed Help Pin
Member 442289727-Jan-14 18:23
memberMember 442289727-Jan-14 18:23 
QuestionHow we can get the selected header [example Switzerland] Pin
Member 1008234729-May-13 4:58
memberMember 1008234729-May-13 4:58 
QuestionHow can we achieve,Grouping(Expanding/collapsing ) of rows Pin
Renand26-Feb-13 19:02
memberRenand26-Feb-13 19:02 
QuestionNice article, but how to add a row expander? Pin
Renand26-Feb-13 1:38
memberRenand26-Feb-13 1:38 
QuestionPerformance issue with the design Pin
Member 457041220-Sep-12 0:16
memberMember 457041220-Sep-12 0:16 
QuestionEditable cells Pin
damianom6-Sep-12 22:17
memberdamianom6-Sep-12 22:17 
QuestionDifferent cell type for each column Pin
Patrick Blackman29-Jun-12 9:13
memberPatrick Blackman29-Jun-12 9:13 
QuestionGreat Article Trying to re-engineer for Silverlight Pin
seesharpme19-Apr-12 3:30
memberseesharpme19-Apr-12 3:30 
AnswerRe: Great Article Trying to re-engineer for Silverlight Pin
seesharpme2-May-12 0:52
memberseesharpme2-May-12 0:52 
Questionneed to help Pin
aminsoro5-Mar-12 2:02
memberaminsoro5-Mar-12 2:02 
QuestionFunctional approach Pin
borisbergman13-Jan-12 3:19
memberborisbergman13-Jan-12 3:19 
QuestionSuperb Pin
Ajit Hegde8-Oct-11 2:55
memberAjit Hegde8-Oct-11 2:55 
QuestionItemsControl within matrix cell Pin
G. Smeets6-Sep-11 23:19
memberG. Smeets6-Sep-11 23:19 
QuestionVery nice Pin
CIDev26-Aug-11 5:37
memberCIDev26-Aug-11 5:37 
GeneralFantastic implementation, very easy to customize Pin
Jeremy Running22-Mar-11 13:06
memberJeremy Running22-Mar-11 13:06 
GeneralScrolling Pin
DanSkin11-Jan-11 2:40
memberDanSkin11-Jan-11 2:40 
QuestionSplitters and Scrollbars Pin
adimas24-Oct-10 21:08
memberadimas24-Oct-10 21:08 
AnswerRe: Splitters and Scrollbars Pin
Josh Smith25-Oct-10 5:41
mvpJosh Smith25-Oct-10 5:41 
GeneralRe: Splitters and Scrollbars Pin
adimas25-Oct-10 6:19
memberadimas25-Oct-10 6:19 
GeneralRe: Splitters and Scrollbars Pin
Josh Smith25-Oct-10 6:59
mvpJosh Smith25-Oct-10 6:59 
GeneralRe: Splitters and Scrollbars Pin
adimas25-Oct-10 9:42
memberadimas25-Oct-10 9:42 
QuestionMake it work in Silverlight 3 or 4? Pin
nycklander3-Jun-10 16:58
membernycklander3-Jun-10 16:58 
QuestionData Matrix nested inside a TreeView? Pin
Sergio Scire14-Apr-10 22:34
memberSergio Scire14-Apr-10 22:34 
NewsAdvanced MVVM Pin
Josh Smith15-Feb-10 23:06
mvpJosh Smith15-Feb-10 23:06 
GeneralIsMouseOver Pin
knew2-Feb-10 8:39
memberknew2-Feb-10 8:39 
GeneralRe: IsMouseOver Pin
Josh Smith5-Feb-10 5:06
mvpJosh Smith5-Feb-10 5:06 
GeneralRe: IsMouseOver Pin
knew8-Feb-10 12:26
memberknew8-Feb-10 12:26 
GeneralExcellent! Pin
gjvdkamp8-Jan-10 5:06
membergjvdkamp8-Jan-10 5:06 
GeneralRe: Excellent! Pin
Josh Smith8-Jan-10 5:08
mvpJosh Smith8-Jan-10 5:08 
GeneralGreat Article Pin
hfourie21-Dec-09 22:16
memberhfourie21-Dec-09 22:16 
Questionany plans for virtualization? Pin
Sergey Galich17-Nov-09 3:24
memberSergey Galich17-Nov-09 3:24 
AnswerRe: any plans for virtualization? Pin
Josh Smith17-Nov-09 5:45
mvpJosh Smith17-Nov-09 5:45 
Generalthank you Pin
MtnGoat9-Nov-09 7:31
memberMtnGoat9-Nov-09 7:31 
GeneralRe: thank you Pin
Josh Smith9-Nov-09 7:33
mvpJosh Smith9-Nov-09 7:33 
GeneralWPF sandbox Pin
petersummers8-Oct-09 5:04
memberpetersummers8-Oct-09 5:04 
QuestionSuggestions for a Silverlight port? Pin
PB_12347-Sep-09 14:12
memberPB_12347-Sep-09 14:12 
AnswerRe: Suggestions for a Silverlight port? Pin
Josh Smith7-Sep-09 19:12
mvpJosh Smith7-Sep-09 19:12 
GeneralRe: Suggestions for a Silverlight port? Pin
Adrian Francies8-Sep-09 6:58
memberAdrian Francies8-Sep-09 6:58 
GeneralGreat Article Pin
Vasudevan Balakrishnan6-Aug-09 11:30
memberVasudevan Balakrishnan6-Aug-09 11:30 
QuestionSelections Pin
knew31-Jul-09 1:11
memberknew31-Jul-09 1:11 
AnswerRe: Selections Pin
Josh Smith31-Jul-09 7:07
mvpJosh Smith31-Jul-09 7:07 
QuestionHow would You proceed when you wanted to make the Values editable? Pin
heuerm16-Jul-09 11:21
memberheuerm16-Jul-09 11:21 
AnswerRe: How would You proceed when you wanted to make the Values editable? Pin
Josh Smith16-Jul-09 11:23
mvpJosh Smith16-Jul-09 11:23 
Generalthis is just the best Pin
moh-heidari11-Jul-09 2:26
membermoh-heidari11-Jul-09 2:26 
GeneralRe: this is just the best Pin
Josh Smith11-Jul-09 7:55
mvpJosh Smith11-Jul-09 7:55 
GeneralRe: this is just the best Pin
mheidari14-Jul-09 22:48
membermheidari14-Jul-09 22:48 
Generalreally useful Pin
Dr.Luiji9-Jul-09 21:05
memberDr.Luiji9-Jul-09 21:05 
GeneralRe: really useful Pin
Josh Smith10-Jul-09 4:24
mvpJosh Smith10-Jul-09 4:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 14 Jun 2009
Article Copyright 2009 by Josh Smith
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid