views:

278

answers:

1

Hi, I was recently, um, chastised for generating and loading XAML markup at runtime using XamlReader.Parse(). I was told that there's no reason to use XamlReader--it can always be done with static XAML, predetermined at design time.

I attempted to ask this person how I could construct a GridView to show pivoted data, where the number of columns and the binding path for each column is unknown at design time. I have yet to hear back, but even if I did, I figured this would be an interesting question to ask, because it might open my eyes (and others') to a better way of doing things.

My specific situation: Each "participant" is expected to perform a certain number of "blocks", but I don't know how many blocks there will be until I grab that data from the database. With that background in mind, my goal is to show a GridView that has the following columns:

Participant | Block 1 | Block 2 | ... | Block N |
(where N is the total number of blocks)

My current strategy is to dynamically loop through the blocks. For each block, I instantiate a new GridViewColumn, generate custom XAML to form a DataTemplate based on the BlockIndex, and use XamlReader.Parse() to set the column's CellTemplate.

Here is the code, just to make sure I'm being clear:

public void AddParticipantGridViewColumns()
{
    GridView view = (GridView)_participantListView.View;
    GridViewColumn column;
    SetupViewModel setupViewModel = (SetupViewModel)DataContext;
    foreach (int blockIndex in setupViewModel.BlockIndices)
    {
        column = BuildParticipantGridViewColumn(blockIndex);
        view.Columns.Add(column);
    }
}

public virtual GridViewColumn BuildParticipantGridViewColumn(int blockIndex)
{
    string templateXaml = string.Format(@"
        <DataTemplate
            xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
            xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
            xmlns:local=""clr-namespace:Pse.ExperimentBase;assembly=ExperimentBase"">        
            <DataTemplate.Resources>
                <local:BlockToBrushConverter
                    x:Key=""_blockToBrushConverter"" />
            </DataTemplate.Resources>
            <TextBlock
                Style=""{{StaticResource _gridViewCenterItemStyle}}""
                Text=""{{Binding Path=Block{0}.ConditionLabel}}""
                Foreground=""{{Binding Path=Block{0}, Converter={{StaticResource _blockToBrushConverter}}}}"" />
        </DataTemplate>",
        blockIndex);
    GridViewColumn column = new GridViewColumn();
    column.Header = string.Format("Block {0}", blockIndex);
    column.CellTemplate = (DataTemplate)XamlReader.Parse(templateXaml);
    return column;
}

Is there some clever way I could do this with static XAML alone or is a dynamic solution, like the one I presented here, the only practical way to do it? Also, if there is a static solution, would you actually prefer it to the dynamic one?

Thanks,

-Dan

A: 

I don't think there is a straightforward way to do it entirely in XAML, but there definitely exists a more static solution than yours. The main problem is not that you are trying to dynamically set up your GridView in the code-behind (that's to be expected with a problem like this), but that you are parsing the same XAML mark-up N times in the code-behind. A DataTemplate such as this should always be found in a mark-up file, not as a string inside a method. In your example, it appears you should define the data-template in the XAML file where your _participantListView is defined.

Charlie
@Charlie, thanks for your answer. I need to correct one thing, though. It's not the same XAML markup every time. Each DataTemlate has unique binding paths for the Text and Foreground properties of the TextBlock.
DanM
In that case by MVVM you would convert those very rich eclectic objects into something uniform and then bind to collection of uniform objects.
Oleg Mihailik
@Oleg, I don't think I understand what you're saying. Each DataTemplate follows a similar pattern, but the binding paths are different. Are you saying the binding path should be a binding itself?
DanM