UserControl inside a FlipView - c#
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
Related
2nd Legend canvas not showing in wpf chart
I've been struggling with this for some time now. The problem relates to adding a second legend canvas in a wpf chart. I'm referencing Jack Yu's book Practical WPF Charts and Graphics LineChartWithLegend.xaml file. In the xaml file, I added the new legend canvas named "legendCanvas2". I've changed the code behind to add a second instance of the legend in the AddChart() method. The problem is the second legend does not show inside chartCanvas. I suspect this issue has to do with multiple canvas containers inside chartCanvas but not sure. Any help with alternative ways I can display two legends inside chartCanvas would be appreciated. XAML <Window x:Class="LineCharts.LineChartWithLegend" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Line Chart with Legend" Height="400" Width="500"> <Grid Name="grid1" Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Name="column1" Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Name="row1" Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Margin="2" x:Name="tbTitle" Grid.Column="1" Grid.Row="0" RenderTransformOrigin="0.5,0.5" FontSize="14" FontWeight="Bold" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextAlignment="Center" Text="Title"/> <TextBlock Margin="2" x:Name="tbXLabel" Grid.Column="1" Grid.Row="2" RenderTransformOrigin="0.5,0.5" TextAlignment="Center" Text="X Axis"/> <TextBlock Margin="2" Name="tbYLabel" Grid.Column="0" Grid.Row="1" RenderTransformOrigin="0.5,0.5" TextAlignment="Center" Text="Y Axis"> <TextBlock.LayoutTransform> <RotateTransform Angle="-90"/> </TextBlock.LayoutTransform> </TextBlock> <Grid Margin="0" x:Name ="chartGrid" Grid.Column="1" Grid.Row="1" ClipToBounds="True" Background="Transparent" SizeChanged="chartGrid_SizeChanged" /> <Canvas Margin="2" Name="textCanvas" ClipToBounds="True" Grid.Column="1" Grid.Row="1"> <Canvas Name="chartCanvas" ClipToBounds="True"> <Canvas Name="legendCanvas" Background="Transparent" /> <Canvas Name="legendCanvas2" Background="Transparent" /> </Canvas> </Canvas> </Grid> </Window> Code-Behind private void AddChart() { cs = new ChartStyleGridlines(); lg = new Legend(); lg2 = new Legend(); dc = new DataCollection(); ds = new DataSeries(); cs.ChartCanvas = chartCanvas; cs.TextCanvas = textCanvas; cs.Title = "Sine and Cosine Chart"; cs.Xmin = 0; cs.Xmax = 7; cs.Ymin = -1.5; cs.Ymax = 1.5; cs.YTick = 0.5; cs.GridlinePattern = ChartStyleGridlines.GridlinePatternEnum.Dot; cs.GridlineColor = Brushes.Black; cs.AddChartStyle(tbTitle, tbXLabel, tbYLabel); // Draw Sine curve: ds.LineColor = Brushes.Blue; ds.LineThickness = 1; ds.SeriesName = "Sine"; for (int i = 0; i < 70; i++) { double x = i / 5.0; double y = Math.Sin(x); ds.LineSeries.Points.Add(new Point(x, y)); } dc.DataList.Add(ds); // Draw cosine curve: ds = new DataSeries(); ds.LineColor = Brushes.Red; ds.SeriesName = "Cosine"; ds.LinePattern = DataSeries.LinePatternEnum.DashDot; ds.LineThickness = 2; for (int i = 0; i < 70; i++) { double x = i / 5.0; double y = Math.Cos(x); ds.LineSeries.Points.Add(new Point(x, y)); } dc.DataList.Add(ds); // Draw sine^2 curve: ds = new DataSeries(); ds.LineColor = Brushes.DarkGreen; ds.SeriesName = "Sine^2"; ds.LinePattern = DataSeries.LinePatternEnum.Dot; ds.LineThickness = 2; for (int i = 0; i < 70; i++) { double x = i / 5.0; double y = Math.Sin(x) * Math.Sin(x); ds.LineSeries.Points.Add(new Point(x, y)); } dc.DataList.Add(ds); dc.AddLines(cs); lg.LegendCanvas = legendCanvas; lg.IsLegend = true; lg.IsBorder = true; lg.LegendPosition = Legend.LegendPositionEnum.NorthWest; lg.AddLegend(cs.ChartCanvas, dc); lg2 = new Legend(); lg2.LegendCanvas = legendCanvas2; lg2.IsLegend = true; lg2.IsBorder = true; lg2.LegendPosition = Legend.LegendPositionEnum.NorthEast; lg2.AddLegend(cs.ChartCanvas, dc); } private void chartGrid_SizeChanged(object sender, SizeChangedEventArgs e) { textCanvas.Width = chartGrid.ActualWidth; textCanvas.Height = chartGrid.ActualHeight; legendCanvas.Children.Clear(); legendCanvas2.Children.Clear(); chartCanvas.Children.RemoveRange(2, chartCanvas.Children.Count - 1); // changed index from 1 to 2 textCanvas.Children.RemoveRange(1, textCanvas.Children.Count - 1); AddChart(); }
Dynamically add BoxViews to Grid [Xamarin.Forms]
I am trying to add BoxViews in a Grid format using 3 columns and multiple rows. I have defined the grid using xaml and the behaviour in the c# file. What should happen is a BoxView should be created for the same number of images with 3 images per column. Thanks, XAML <Grid RowSpacing="0" x:Name="scrollBarGrid"> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--Where the search bar will go--> <BoxView BackgroundColor="Aqua" Grid.Row="0"/> <SearchBar ></SearchBar> <!--Where the images will go--> <BoxView BackgroundColor="Gray" Grid.Row="1"/> <Grid x:Name="imageGrid" RowSpacing="0" Grid.Row="1"> </Grid> </Grid> C# public MainPage() { InitializeComponent(); int colMaximum = 3; int numberOfImages = 15; //To add three columns for (int i = 0; i < colMaximum; i++) { imageGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(120, GridUnitType.Absolute) }); } //To add an array of rows imageGrid.RowDefinitions = new RowDefinitionCollection(); for (int myCount = 0; myCount <= numberOfImages / colMaximum; myCount++) { imageGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(120, GridUnitType.Absolute) }); //To add a new box view for each for (int newcol = 0; newcol <= colMaximum; newcol++) { for (int newrow = 0; newrow <= numberOfImages / colMaximum; newrow++) { imageGrid.Children.Add(new BoxView() { BackgroundColor = Color.Red }); } } } }
When you add children to a grid, you have to specify the Row and Col, otherwise they will be added at 0,0. imageGrid.Children.Add(new BoxView() { BackgroundColor = Color.Red }, newrow, newcol);
Displaying grid lines around individual pixels when zooming
I'm experimenting with the concept of drawing grid lines over a control and was wondering what adjustments I might need to make to make this actually work. I found some code on another post that enables grid lines to be drawn OnRender over a canvas. Here's what that looks like: public class MyCanvas : Canvas { public bool IsGridVisible = true; protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (IsGridVisible) { // Draw GridLines Pen pen = new Pen(Brushes.Black, 1); pen.DashStyle = DashStyles.Solid; for (double x = 0; x < this.ActualWidth; x += 2) { dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight)); } for (double y = 0; y < this.ActualHeight; y += 2) { dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y)); } } } public MyCanvas() { DefaultStyleKey = typeof(MyCanvas); } } This part: y += 2 indicates how many other pixels/points to wait before drawing next line, though I am uncertain of it's correctness. Here's the xaml: <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <ScrollViewer> <local:MyCanvas> <local:MyCanvas.LayoutTransform> <ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/> </local:MyCanvas.LayoutTransform> <Image Canvas.Top="2" Canvas.Left="2" Source="C:\Users\Me\Pictures\nyan-wallpaper2.jpg" Width="325" RenderOptions.BitmapScalingMode="NearestNeighbor"/> </local:MyCanvas> </ScrollViewer> <Slider x:Name="Slider" Maximum="500" Grid.Row="1" Value="1"/> </Grid> Here are screenshots of what the above results in. As you can see, the grid lines change in size as you zoom and the lines themselves do not snap around each individual pixel. I highlighted an example pixel in red to show how small the lines should be versus how they actually are. I read that the thickness of the pen should be divided by the scale value, however, I tested this by replacing Pen pen = new Pen(Brushes.Black, 1); with Pen pen = new Pen(Brushes.Black, 1 / 3); and set the ScaleX and ScaleY of MyCanvas to 3. At that point, no lines showed at all. Any help at all is immensely valued!
Got it working like this for anyone curious: MainWindow.xaml.cs namespace Test { public class MyCanvas : Canvas { public bool IsGridVisible = false; #region Dependency Properties public static DependencyProperty ZoomValueProperty = DependencyProperty.Register("ZoomValue", typeof(double), typeof(MyCanvas), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnZoomValueChanged)); public double ZoomValue { get { return (double)GetValue(ZoomValueProperty); } set { SetValue(ZoomValueProperty, value); } } private static void OnZoomValueChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e) { } #endregion protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); IsGridVisible = ZoomValue > 4.75 ? true : false; if (IsGridVisible) { // Draw GridLines Pen pen = new Pen(Brushes.Black, 1 / ZoomValue); pen.DashStyle = DashStyles.Solid; for (double x = 0; x < this.ActualWidth; x += 1) { dc.DrawLine(pen, new Point(x, 0), new Point(x, this.ActualHeight)); } for (double y = 0; y < this.ActualHeight; y += 1) { dc.DrawLine(pen, new Point(0, y), new Point(this.ActualWidth, y)); } } } public MyCanvas() { DefaultStyleKey = typeof(MyCanvas); } } /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private WriteableBitmap bitmap = new WriteableBitmap(500, 500, 96d, 96d, PixelFormats.Bgr24, null); private void Button_Click(object sender, RoutedEventArgs e) { int size = 1; Random rnd = new Random(DateTime.Now.Millisecond); bitmap.Lock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests. for (int y = 0; y < 500; y++) { for (int x = 0; x < 500; x++) { byte colR = (byte)rnd.Next(256); byte colG = (byte)rnd.Next(256); byte colB = (byte)rnd.Next(256); DrawRectangle(bitmap, size * x, size * y, size, size, Color.FromRgb(colR, colG, colB)); } } bitmap.Unlock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests. Image.Source = bitmap; // This should be done only once } public void DrawRectangle(WriteableBitmap writeableBitmap, int left, int top, int width, int height, Color color) { // Compute the pixel's color int colorData = color.R << 16; // R colorData |= color.G << 8; // G colorData |= color.B << 0; // B int bpp = writeableBitmap.Format.BitsPerPixel / 8; unsafe { for (int y = 0; y < height; y++) { // Get a pointer to the back buffer int pBackBuffer = (int)writeableBitmap.BackBuffer; // Find the address of the pixel to draw pBackBuffer += (top + y) * writeableBitmap.BackBufferStride; pBackBuffer += left * bpp; for (int x = 0; x < width; x++) { // Assign the color data to the pixel *((int*)pBackBuffer) = colorData; // Increment the address of the pixel to draw pBackBuffer += bpp; } } } writeableBitmap.AddDirtyRect(new Int32Rect(left, top, width, height)); } } } MainWindow.xaml <Window x:Class="Test.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Test" mc:Ignorable="d" Title="MainWindow" Height="Auto" Width="Auto" WindowStartupLocation="CenterScreen" WindowState="Maximized"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <ScrollViewer> <local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}"> <local:MyCanvas.LayoutTransform> <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/> </local:MyCanvas.LayoutTransform> <Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/> </local:MyCanvas> </ScrollViewer> <StackPanel Grid.Row="1" Orientation="Horizontal"> <Slider x:Name="Slider" Maximum="100" Minimum="0.5" Value="1" Width="200"/> <Button Click="Button_Click" Content="Click Me!"/> </StackPanel> </Grid> </Window> We generate a bitmap with random colored pixels and then render the grid lines only if zoomed up close. Performance-wise, this is actually better than expected. I should note, though, that if you attempt to zoom below 50%, the app crashes. Not sure if it's an issue with the grid lines being drawn at a minute size (IsGridVisible = true where ZoomValue < 0.5) or with the bitmap being generated. Either way, cheers! Update Didn't realize the grid lines are still behind the contents of the canvas. Haven't worked out a solution for that yet... Update 2 Replace: <local:MyCanvas ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}"> <local:MyCanvas.LayoutTransform> <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/> </local:MyCanvas.LayoutTransform> <Image Canvas.Top="1" Canvas.Left="1" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/> </local:MyCanvas> With: <Grid> <Canvas> <Canvas.LayoutTransform> <ScaleTransform ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/> </Canvas.LayoutTransform> <Image Canvas.Top="5" Canvas.Left="5" x:Name="Image" RenderOptions.BitmapScalingMode="NearestNeighbor"/> </Canvas> <local:MyGrid ZoomValue="{Binding ElementName=ScaleTransform, Path=ScaleX}"> <local:MyGrid.LayoutTransform> <ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ElementName=Slider, Path=Value}" ScaleY="{Binding ElementName=Slider, Path=Value}"/> </local:MyGrid.LayoutTransform> </local:MyGrid> </Grid> I believe another boost in performance as we are utilizing a simpler control to display the grid lines, plus, the grid lines can be placed either below or above desired controls. Update 3 I have decided to post my latest solution, which is significantly more efficient and can all be done in XAML: <Grid> <Grid.Background> <DrawingBrush Viewport="0,0,5,5" ViewportUnits="Absolute" TileMode="Tile"> <DrawingBrush.Drawing> <DrawingGroup> <DrawingGroup.Children> <GeometryDrawing Geometry="M-.5,0 L50,0 M0,10 L50,10 M0,20 L50,20 M0,30 L50,30 M0,40 L50,40 M0,0 L0,50 M10,0 L10,50 M20,0 L20,50 M30,0 L30,50 M40,0 L40,50"> <GeometryDrawing.Pen> <Pen Thickness="1" Brush="Black" /> </GeometryDrawing.Pen> </GeometryDrawing> </DrawingGroup.Children> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Grid.Background> </Grid>
SetView in Windows Phone map with desired locations, margin and center point
I have added a map in my Windows Phone 8 application. Now I want is to view one desired point with a center point on the map. XAML: <Grid x:Name="ContentPanel" Grid.RowSpan="2"> <Grid.RowDefinitions> <RowDefinition Height="68" /> <RowDefinition Height="1" /> <RowDefinition Height="65" /> <RowDefinition Height="1" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <maps:Map x:Name="map" Grid.RowSpan="5" Height="800" /> <Image Source="/Assets/Images/Pin.png" Width="35" Height="55" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.RowSpan="5" Canvas.ZIndex="15"/> <Button Grid.Row="0" Content="Search" /> <Button Grid.Row="4" Content="Check" /> </Grid> View Model C# MapRectLocation zoomLocation = new MapRectLocation(); zoomLocation.CenterPoint = new Location() { lat = CenterLocation.Latitude, lng = CenterLocation.Longitude }; zoomLocation.Locationx = new Location(); zoomLocation.Locationx.Latitude = FirstLocation.Latitude; zoomLocation.Locationx.Longitude= FirstLocation.Longitude; // Calculate the other point for boundary zoomLocation.Locationy= GetEqivalentPoint(CenterLocation, FirstLocation); public Location GetEqivalentPoint(System.Device.Location.GeoCoordinate CenterLocation, Location location) { var dlat = CenterLocation.Latitude - location.lat; var dlng = CenterLocation.Longitude - location.lng; Location equiPoint = new Location(); equiPoint.lat = CenterLocation.Latitude + dlat; equiPoint.lng = CenterLocation.Longitude + dlng; return equiPoint; } XAML.cs Code: List<GeoCoordinate> zoomBoundaries = new List<GeoCoordinate>(); zoomBoundaries.Add(new GeoCoordinate(mapViewLocation.Locationx.lat, mapViewLocation.Locationx.lng)); zoomBoundaries.Add(new GeoCoordinate(mapViewLocation.Locationy.lat, mapViewLocation.Locationy.lng)); map.SetView(LocationRectangle.CreateBoundingRectangle(zoomBoundaries), new Thickness(0, 150, 0, 300)); The map sets to desired zoom level to display that point but the problem is it does not keep the same centerpoint as earlier. I want to keep the Centerlocation as same and display FirstLocation point on the map. I am also setting margin in Setview as to keep that First location point above the 2 buttons that I have in my XAML code overlaying map. Please let me know how to rectify this problem?
Try this: void SetLoc() { MyMap.Layers.Clear(); try { // ... get the coordinates "myGeoCoordinate" // Make my current location the center of the Map. this.MyMap.Center = myGeoCoordinate; this.MyMap.ZoomLevel = 12; // Create a small circle to mark the current location. Ellipse myCircle = new Ellipse(); myCircle.Fill = new SolidColorBrush(Colors.Blue); myCircle.Height = 20; myCircle.Width = 20; myCircle.Opacity = 50; // Create a MapOverlay to contain the circle. MapOverlay myLocationOverlay = new MapOverlay(); myLocationOverlay.Content = myCircle; myLocationOverlay.PositionOrigin = new Point(0.5, 0.5); myLocationOverlay.GeoCoordinate = myGeoCoordinate; // Create a MapLayer to contain the MapOverlay. MapLayer myLocationLayer = new MapLayer(); myLocationLayer.Add(myLocationOverlay); // Add the MapLayer to the Map. MyMap.Layers.Add(myLocationLayer); } catch { } }
Not sure which property should I use for animation
I am creating a kiosk app in Windows 8 but it will be used as assigned access app in 8.1. I want to create an animation for ads. The idea of animation is attached as image with this thread. Basically there will be 6-10 images in a L shape (Right side a column & bottom side a row). Now one ad in extreme bottom right corner will be stationary. The ads in column will travers like HTML's marquee and reaches to row at that time the ads in row will travers and reaches to column. In this way the ads will keep of moving in a clock wise pattern. How can I achieve this in my C#/XAML app? Please not the ads will be never be displayed on top or left. The ad is <Image /> & source is Internet URLs. All ads are in ItemsControl.
Yuck, let's start with how much I hate this app already. Now to the answer. I don't think you really need an animation. You will likely change the ads only every so often. And a simple change in their position and NOT a transition in their position seems adequate. Try this: <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Rectangle Fill="White" Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="0" Grid.RowSpan="3" /> <Rectangle x:Name="Ad1" Fill="Green" Grid.Column="3" Grid.Row="3" /> <Rectangle x:Name="Ad2" Fill="IndianRed" Grid.Column="0" Grid.Row="3" /> <Rectangle x:Name="Ad3" Fill="Red" Grid.Column="1" Grid.Row="3" /> <Rectangle x:Name="Ad4" Fill="DarkRed" Grid.Column="2" Grid.Row="3" /> <Rectangle x:Name="Ad5" Fill="Pink" Grid.Column="3" Grid.Row="0" /> <Rectangle x:Name="Ad6" Fill="HotPink" Grid.Column="3" Grid.Row="1" /> <Rectangle x:Name="Ad7" Fill="Purple" Grid.Column="3" Grid.Row="2" /> </Grid> With this: public MainPage() { this.InitializeComponent(); var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += (s, e) => Move(); timer.Start(); } void Move() { var ads = new Rectangle[] { Ad1, Ad2, Ad3, Ad4, Ad5, Ad6, Ad7 }; foreach (var item in ads) { var row = (int)item.GetValue(Grid.RowProperty); var col = (int)item.GetValue(Grid.ColumnProperty); if (row == 3) { if (col == 0) { row = 0; col = 3; } else col--; } else { if (row == 2) { row = 3; col = 2; } else row++; } item.SetValue(Grid.RowProperty, row); item.SetValue(Grid.ColumnProperty, col); } } And it looks pretty good to me. But if you must have animations, try this. void Move() { var ads = new Rectangle[] { Ad1, Ad2, Ad3, Ad4, Ad5, Ad6, Ad7 }; foreach (var item in ads) { var row = (int)item.GetValue(Grid.RowProperty); var col = (int)item.GetValue(Grid.ColumnProperty); var x = item.ActualWidth; var y = item.ActualHeight; // bottom if (row == 3) { // left-last if (col == 0) { row = 0; col = 3; x = -x; y = 0; } // others else { col--; x = -x; y = 0; } } // right else { // bottom-last if (row == 2) { row = 3; col = 2; x = -x; } else { row++; x = 0; } } var dr = new Duration(TimeSpan.FromSeconds(.5)); var tx = item.RenderTransform = new TranslateTransform(); var ax = new DoubleAnimation { To = x, Duration = dr }; Storyboard.SetTarget(ax, tx); Storyboard.SetTargetProperty(ax, "X"); var ay = new DoubleAnimation { To = y, Duration = dr }; Storyboard.SetTarget(ay, tx); Storyboard.SetTargetProperty(ay, "Y"); var st = new Storyboard { FillBehavior = FillBehavior.HoldEnd }; st.Children.Add(ax); st.Children.Add(ay); st.Completed += (s, e) => { item.SetValue(Grid.RowProperty, row); item.SetValue(Grid.ColumnProperty, col); st.Stop(); }; st.Begin(); } } Best of luck!