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;
}
}
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.