Summary: I cannot get a UserControl, which is inside a FlipView, to refresh/update itself when changing pages on the FlipView. The UserControl depends upon its code-behind to perform essential calculations for its own display.
Introduction: I have created a graphing UserControl in Windows 8 due to the lack of such a control at the moment (apart from some 3rd parties). The XAML for the control creates the axes and declares the path used for actually drawing the graph-line, and the code-behind instantiates a class which generates the plot-points for this path. The instantiation of this graph-path class is made by the loaded event-handler in the code-behind once the axes have been properly rendered in order to get the size of the plotting area. The parameters to the constructor include various data specific to the plot-points plus references to the child control elements which are used for scaling.
Here's the control:
<Grid>
<Border Background="LightGray"
Margin="0,10">
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="1"
Grid.Column="0"
Grid.RowSpan="11"
Name="yAxisBorder">
<!--this canvas is written to in DrawGraph.cs -->
<Canvas Name="yAxis">
</Canvas>
</Border>
<Border Grid.Row="11"
Grid.Column="1"
Name="xAxisBorder"
BorderBrush="Black">
<!--this canvas is written to in DrawGraph.cs -->
<Canvas Name="xAxis"/>
</Border>
<Border x:Name ="GraphAxis"
Grid.Row="1"
Grid.Column="1"
Grid.RowSpan="10"
BorderThickness="2,2,2,2"
BorderBrush="Black"
Loaded="GraphAxis_Loaded">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop x:Name="GraphStop2"
Offset="0.554" Color="White"/>
</LinearGradientBrush>
</Border.Background>
<!--this path is written to in DrawGraph.cs -->
<Path x:Name="GraphLine"
StrokeThickness="2"
Data="M0,0">
<Path.Stroke>
<SolidColorBrush Color="Black"/>
</Path.Stroke>
<Path.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="LightPink"
Offset="1"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
</Border>
<!--These borders draw the grid-lines on the graph-->
<Border Grid.Row="1"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black"/>
<Border Grid.Row="2"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="3"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="4"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<!--This border is for drawing the x-axis for -ve inflation-->
<Border Grid.Row="5"
Grid.Column="1"
x:Name="xAxisLine1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="6"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="7"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="8"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border Grid.Row="9"
Grid.Column="1"
BorderThickness="0,0,0,0.5"
BorderBrush="Black">
</Border>
<Border x:Name="xAxisLine2"
Grid.Row="10"
Grid.Column="1"
BorderThickness="0,0,0,2"
BorderBrush="Black">
</Border>
</Grid>
</Border>
</Grid>
Here's the code-behind with the instantiation of the object which draws the graph line (the Path called GraphLine in the XAML:
public void GraphAxis_Loaded(object sender, RoutedEventArgs e)
{
ApplicationDataContainer appData = ApplicationData.Current.RoamingSettings;
Country selectedCountry = CountryDataSource.GetCountry((int)appData.Values["Country"]);
string month1 = selectedCountry.FirstMonth, month2 = selectedCountry.LastMonth;
int year1 = selectedCountry.FirstYear, year2 = selectedCountry.LastYear;
DrawGraph dGraph = new DrawGraph(selectedCountry, month1, year1, month2, year2,
GraphAxis.ActualHeight, GraphAxis.ActualWidth, GraphLine, xAxis, xAxisBorder, yAxis, MainGrid, xAxisLine1, xAxisLine2);
}
}
...and finally here's the DrawGraph class:
public class DrawGraph
{
List<String> months = new List<string> { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
public DrawGraph(Country currentC, string nearMonth, int nearYear, string farMonth, int farYear, double cHeight, double cWidth, Path pGraphLine, Canvas xAxis, Border xAxisBorder, Canvas yAxis, Grid GraphGrid, Border xAxisLine1, Border xAxisLine2)
{
int periods = 0, years = farYear - nearYear, firstDateIndex = 0, lastDateIndex = 0, n = 0, prIndex = 0, yScalePve = 0, yScaleNve = 0, yScaleExtent = 0;
double endDateIndex, minFactor = 1000, maxFactor = 0, inflationFactor,
baseFactor = 0, yOffSet = 0;
plotPoint[] plotResults;
cHeight -= 4; // this is for the 4 pixels taken up by the border
// count up the months between the start and end points
foreach (KeyValuePair<string, double> rpiRecord in currentC.RpiData)
{
if (rpiRecord.Key == nearMonth + nearYear.ToString())
{
firstDateIndex = n;
}
if (rpiRecord.Key == farMonth + farYear.ToString())
{
lastDateIndex = n;
}
n++;
}
// calculate the number of points to plot
periods = lastDateIndex - firstDateIndex + 1;
// put together the array of dates (relative) and factors to plot
if (periods > 1)
{
plotResults = new plotPoint[periods];
n = 0;
foreach (KeyValuePair<string, double> rpiRecord in currentC.RpiData)
{
if (n >= firstDateIndex & n <= lastDateIndex)
{
if (n == firstDateIndex)
{
baseFactor = rpiRecord.Value;
plotResults[prIndex] = new plotPoint(prIndex, 0);
minFactor = 0;
maxFactor = 0;
}
else
{
// get the inflation factor and date position and populate the plotPoints array
inflationFactor = (rpiRecord.Value - baseFactor) / baseFactor * 100;
plotResults[prIndex] = new plotPoint(prIndex, inflationFactor);
// these are used for the height scaling
if (inflationFactor > maxFactor)
{
maxFactor = inflationFactor;
}
if (inflationFactor < minFactor & rpiRecord.Value != 0)
{
minFactor = inflationFactor;
}
}
prIndex++;
}
n++;
}
// now decide on the y-axis scale
// firstly, is the maximum in the units, tens, hundreds or thousands
if (maxFactor - 1 < 0)
{
yScalePve = 1;
}
else if (maxFactor - 2 < 0)
{
yScalePve = 2;
}
else if (maxFactor - 5 < 0)
{
yScalePve = 5;
}
else if (maxFactor - 10 < 0)
{
yScalePve = 10;
}
else if (maxFactor - 50 < 0)
{
yScalePve = 50;
}
else if (maxFactor - 100 < 0)
{
yScalePve = 100;
}
else if (maxFactor - 500 < 0)
{
yScalePve = 500;
}
else if (maxFactor - 1000 < 0)
{
yScalePve = 1000;
}
else if (maxFactor - 5000 < 0)
{
yScalePve = 5000;
}
else
{
yScalePve = 10000;
}
if (minFactor < 0)
{
if (minFactor + 1 > 0)
{
yScaleNve = 1;
}
else if (minFactor + 2 > 0)
{
yScaleNve = 2;
}
else if (minFactor + 5 > 0)
{
yScaleNve = 5;
}
else if (minFactor + 10 > 0)
{
yScaleNve = 10;
}
else if (minFactor + 50 > 0)
{
yScaleNve = 50;
}
else if (minFactor + 100 > 0)
{
yScaleNve = 100;
}
else if (minFactor + 500 > 0)
{
yScaleNve = 500;
}
else if (minFactor + 1000 > 0)
{
yScaleNve = 1000;
}
else if (minFactor + 5000 > 0)
{
yScaleNve = 5000;
}
else
{
yScaleNve = 10000;
}
// calculate the position of the xAxis on the yScale
if (maxFactor <= minFactor * -100) // this is to prevent small -ves moving the x-axis to the middle
{
yOffSet = cHeight / 2;
}
}
// calculate the position of the xAxis on the yScale
yScaleExtent = yScalePve + yScaleNve;
if (cHeight > 0) // this prevents the borders being re-sized before the grids have been rendered
{
// this re-sizes the rows in the grid displaying the graph - there is a border in the 5th row (defined in the XAML)
// which acts as the x axis and this adjusts the line thickness to where the x axis should be
if (yOffSet == 0)
{
// this draws the x-axis when it is at the bottom position
xAxisLine1.BorderThickness = new Thickness(0, 0, 0, 1);
xAxisLine2.BorderThickness = new Thickness(0, 0, 0, 2);
cHeight += 1; // this is for the extra pixel taken up by the border
}
else
{
// this draws the x-axis when it is at the middle position
xAxisLine1.BorderThickness = new Thickness(0, 0, 0, 2);
xAxisLine2.BorderThickness = new Thickness(0, 0, 0, 1);
cHeight -= 1; // this is for the extra pixel taken up by the border
}
}
// this is used for the width scaling
endDateIndex = plotResults[plotResults.Length - 1].plotDate;
// zero-base the plot results
plotResults[0].plotDate = 0;
plotResults[0].plotFactor = 0 + yOffSet;
PathFigure myPathFigure = new PathFigure();
myPathFigure.StartPoint = new Point(0, cHeight - yOffSet);
PathSegmentCollection myPathSegmentCollection = new PathSegmentCollection();
for (int x = 1; x < plotResults.Length; x++)
{
// re-base the array dates with respect to the plot area width
plotResults[x].plotDate *= cWidth / endDateIndex;
// re-base the array factors with respect to the y-axis
plotResults[x].plotFactor = plotResults[x].plotFactor / yScaleExtent;
// then re-base the array into the units of the plot area
plotResults[x].plotFactor = plotResults[x].plotFactor * (cHeight - yOffSet);
// generate the LineSegment objects for each plotPoint
LineSegment myLineSegment = new LineSegment();
myLineSegment.Point = new Point(plotResults[x].plotDate, (cHeight - yOffSet) - plotResults[x].plotFactor);
myPathSegmentCollection.Add(myLineSegment);
}
// these 2 additional line segments create an enclosed polygon for the fill to go into
LineSegment myLineSegment1 = new LineSegment();
// this line draws down vertically on the right-hand y axis
myLineSegment1.Point = new Point(cWidth, cHeight - yOffSet); //
myPathSegmentCollection.Add(myLineSegment1);
LineSegment myLineSegment2 = new LineSegment();
// this line draws across to the left on the x axis
myLineSegment2.Point = new Point(0, cHeight - yOffSet);
myPathSegmentCollection.Add(myLineSegment2);
// this sets up the LineSegments for the PathFigure, the
// PathFigure for the PathGeometry and the PathGeometry for the PathData!!
myPathFigure.Segments = myPathSegmentCollection;
PathFigureCollection myPathFigureCollection = new PathFigureCollection();
myPathFigureCollection.Add(myPathFigure);
PathGeometry myPathGeometry = new PathGeometry();
myPathGeometry.Figures = myPathFigureCollection;
pGraphLine.Data = myPathGeometry;
Calculate_xAxis(nearYear, farYear, nearMonth, farMonth, xAxis, xAxisBorder);
Calculate_yAxis(yScalePve, yScaleNve, xAxis, yAxis, yOffSet);
}
}
Issue: I want to display this graphing control within a FlipView which is bound to an observable collection which holds sets of data used by both the FlipView and the path-drawing class. As you can see in the code-behind, there is no data binding in the graph control itself. The data is obtained from the ApplicationData and control element sizes within the UserControl itself. This all works fine when the FlipView starts up but when I flip the pages the graphs do not update.
I have looked at this problem from a couple of points-of-view:
Firstly, it appears to be the case that when flipping the pages in the FlipView, the loaded event does not re-fire on the axis control (a Border) within the UserControl within the FlipView, but it does fire first time around. I cannot find any other event to hook into other than the loaded event.
Secondly, would it be possible to make the entire graph work off data-binding? I understand that I'd have to create Dependency Properties in the UserControl to do this but even then, I would still need to instantiate a DrawGraph in code-behind somehow.
Can anyone here see if there is a solution to this problem? Or am I approaching this incorrectly? Should I be putting all of the DrawGraph functionality into a routine somewhere else which pre-populates the Observable Collection with all of the plot-points and then bind the graph UserControl to this?
Any suggestions most welcome. I'm fairly new to this and this is my first posting here and am hopeful that someone can educate me in my approach. Thank you