Question

I want to create a DataGrid with two columns (X and Y), being that each may be populated by different collections.

The Y column is always populated with the Y data from an ObservableCollection(NPoint), where NPoint is a class with X and Y properties.

The X column starts being populated with the "default" values defined elsewhere (an ObservableCollection(double)). This "default" collection belongs to a singleton class. However, depending on the .IsChecked of a nearby CheckBox, the X column might be populated with the X data from the same collection as the Y data.

The latter case is trivial, since both columns will share the same ItemsSource. However, how can one bind one column of a DataGrid to one object and another column to another object? Is there a way to bind the DataGrid.ItemsSource to two different collections? Can this be done with Multi- or PriorityBinding?

Was it helpful?

Solution

After reading @techhero's answer, I had a crazy idea which I managed to pull off (thanks to Tao Ling's answer to this question). It isn't perfect, but it does the trick. I basically split the DataGrid into two, one with the X column (with variable .ItemsSource) and one with the Y column, one right next to the other.

Here's the relevant code:

XAML

<DataGrid Grid.Column="0"
            x:Name="CoordinatesX"
            LoadingRow="RowIndexX"
            VerticalScrollBarVisibility="Disabled"
            AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="X" x:Name="XColumn"/>
    </DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Column="1"
            x:Name="CoordinatesY"
            ItemsSource="{Binding DataContext.Points, ElementName=CableTab}" 
            LoadingRow="RowIndexY"
            RowHeaderWidth="0"
            ScrollViewer.ScrollChanged="ScrollChanged"
            VerticalScrollBarVisibility="Visible"
            AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Y"/>
    </DataGrid.Columns>
</DataGrid>

.CS

private void RowIndexX(object sender, DataGridRowEventArgs e)
{
    e.Row.Header = (e.Row.GetIndex() + 1).ToString();
}
private void RowIndexY(object sender, DataGridRowEventArgs e)
{
    e.Row.Header = " ";
}
void ControlBindings()
{
    var resultSections = NProjectProperties.Instance.ResultSections;
    var cable = DataContext as NCable;
    Binding binding;
    if (EqualToResults.IsChecked == true)
    {
        CoordinatesX.ItemsSource = resultSections;
        XColumn.IsReadOnly = true;
        XColumn.Foreground = Brushes.DarkGray;
        binding = new Binding();
    }
    else
    {
        CoordinatesX.ItemsSource = cable.Points;
        XColumn.IsReadOnly = false;
        XColumn.Foreground = Brushes.Black;
        binding = new Binding("X");
    }
    binding.ValidatesOnDataErrors = true;
    binding.NotifyOnValidationError = true;
    XColumn.Binding = binding;
}
private void EqualToResultsChanged(object sender, RoutedEventArgs e)
{
    ControlBindings();
}
private void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    var scroll1 = NU.GetDescendantByType(CoordinatesX, typeof(ScrollViewer)) as ScrollViewer;
    var scroll2 = NU.GetDescendantByType(CoordinatesY, typeof(ScrollViewer)) as ScrollViewer;
    scroll1.ScrollToVerticalOffset(scroll2.VerticalOffset);
}

public static class NU
{
    public static Visual GetDescendantByType(Visual element, Type type)
    {
        if (element == null) return null;
        if (element.GetType() == type) return element;
        Visual foundElement = null;
        if (element is FrameworkElement)
        {
            (element as FrameworkElement).ApplyTemplate();
        }
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
        {
            Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
            foundElement = GetDescendantByType(visual, type);
            if (foundElement != null)
                break;
        }
        return foundElement;
    }
}

Resulting window

The ScrollViewer.ScrollChanged="ScrollChanged" line is what allows the DataGrids to scroll together. However, the first DataGrid has a row header, which for some reason makes the rows slightly bigger. This means that, if the second one wasn't given a header, they wouldn't be aligned. Therefore the second DataGrid is given a LoadingRowY function which returns an " " header which doesn't appear since the RowHeaderWidth="0". This does however make the rows be drawn in the same scale as those of the first DataGrid, aligning them.

As can be seen, the space between the two DataGrids isn't very nice and clean and should be improved, but I'm satisfied for now.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top