Double tap to zoom Windows Phone - c#

I've a little problem with double tap to zoom, when I tap twice the image enlarge but I can see only half image and I can't move image to see other half of image. How can I for see all image?
The code is:
[XAML]
<!--LayoutRoot è la griglia radice in cui viene inserito tutto il contenuto della pagina-->
<Grid x:Name="LayoutRoot" Background="#FF0A5BC4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--ContentPanel - inserire ulteriore contenuto qui-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel Margin="-12,10,-11,98" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="immagine_Name" Margin="1,1,1,1" Stretch="UniformToFill">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener PinchStarted="GestureListener_PinchStarted"
PinchDelta="GestureListener_PinchDelta"
DoubleTap="GestureListener_DoubleTap"/>
</toolkit:GestureService.GestureListener>
<Image.RenderTransform>
<CompositeTransform x:Name="transform" ScaleX="1" ScaleY="1" TranslateX="0" TranslateY="0"/>
</Image.RenderTransform>
</Image>
</StackPanel>
<StackPanel Background="#FF094AB2" Margin="-12,675,-11,0">
</StackPanel>
</Grid>
</Grid>
[C#]
private void GestureListener_DoubleTap(object sender, GestureEventArgs e)
{
if (transform.ScaleX == 1 && transform.ScaleY == 1) //The scale is currently 1, enlarge
{
transform.ScaleX = 1.5;
transform.ScaleY = 1.5;
}
else
{ //Its bigger, reset to 1.
transform.ScaleX = 1;
transform.ScaleY = 1;
}
}
This is the image to normal size:
http://i.imgur.com/vlUlbes.jpg
And this is the image after double tap, that I can't move for see the rest of image:
http://i.imgur.com/AZwsccq.jpg
Thank you for all who help me.

Related

ScrollViewer not showing all content when canvas is scaled using ScaleTransform

I have two Canvas panels in ScrollViewer. One is the main canvas which is having a grid shape drawn on its back ground. Then I have two ItemsControl. The first ItemsControl is having Stackpanel as its ItemsPanel with Horizontal Orientation. The second ItemsControl is having Canvas as its Panel. On this canvas I am drawing Line objects in DataTemplate of Itemscontrol.There is PreviewMouseWheel event on this canvas. In the event handler I am zooming this canvas which is zooming the Line objects. The width of this canvas is binded to ViewModel property CanvasWidth. Also this will change the width of Outer Canvas as its width is also binded to ViewModel Property CanvasWidth. When the PreviewMouseWheel is fired, I am adding more grid lines on main Canvas. I have TextBlock over them as DataTemplate of ItemsSource. Before zooing, the content of last TextBlock was 14260. After zoomin it should remain 14260. but the step value of two consecutive TextBlock should be reduced. Right now I am not able to see the whole content through ScrollViewer. The step size is reduced which was desired but the new grid lines which are drawn cannot be seen throught Scrollviewer. i know there is content. but I am unable to acces it. The scrollviewer is not showing it.
<Grid x:Name="grid1" >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ScrollViewer Name="scrollViewer" HorizontalScrollBarVisibility="Auto" Grid.Row="1" Grid.Column="3" Margin="10,10,0,10" >
<Canvas Name="back_canvas" Height="12000" Width="{Binding CanvasWidth}" Margin="0,0,10,0" >
<Canvas.Background>
<DrawingBrush TileMode="Tile" Viewport="0,0,40,40" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,50,50"/>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="Gray" Thickness="1"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Canvas.Background>
<ItemsControl ItemsSource="{Binding TimeAxis}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Margin="0,0,3,0" Width="37" Background="GreenYellow" >
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="12000" Background="Transparent" Name="front_canvas"
PreviewMouseWheel="OnPreviewMouseWheel"
Width="{Binding CanvasWidth, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
</Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ScrollViewer>
</Grid>
private void UpdateGraph(Canvas canvas, double deltaValue)
{
List<MarkerView> markers = new List<MarkerView>();
scaleFactor += deltaValue;
double tempScale = scaleFactor;
if (scaleFactor < 1.0)
{
scaleFactor = 1.0;
}
if (scaleFactor > maximumScale)
{
scaleFactor = maximumScale;
}
if (tempScale > 0)
{
totalSamples = graphVM.maxSignalLength;
maximumCanvasWidth = totalSamples * maximumDeltaDistance;
if(scaleFactor<=maximumDeltaDistance)
{
ScaleTransform scaleTransform = new ScaleTransform(scaleFactor, 1);
canvas.RenderTransform = scaleTransform;
verticalLines.ForEach(x =>
{
x.RenderTransformOrigin = new Point(1, 1);
x.RenderTransform = new ScaleTransform(1 / scaleTransform.ScaleX, 1 / scaleTransform.ScaleY);
});
if (deltaValue < 0)
{
graphVM.CanvasWidth = graphVM.InitialCanvasWidth * tempScale;
}
else
{
if (graphVM.InitialCanvasWidth * scaleFactor > maximumCanvasWidth)
graphVM.CanvasWidth = maximumCanvasWidth;
else
graphVM.CanvasWidth = graphVM.InitialCanvasWidth * scaleFactor;
}
graphVM.ResetLabels();
DeltaDistance = canvas.Width / totalSamples;
MarkerView markerRed =
UIHelperView.FindChild<MarkerView>(Application.Current.MainWindow, "splitterRed");
MarkerView markerGreen =
UIHelperView.FindChild<MarkerView>(Application.Current.MainWindow, "splitterGreen");
markers.Add(markerRed);
markers.Add(markerGreen);
// Move Markers with zooming
foreach (MarkerView marker in markers)
{
marker.Delta = DeltaDistance; // after zooming if you move the marker then this value will be used to get correct position
Canvas.SetLeft(marker, marker.XPosition * DeltaDistance);
}
markers.Clear();
}
}
}
here is the output picture https://imgur.com/a/7WTrBoc
this is zoomed output https://imgur.com/C7SCOSJ
RenderTransform doesn't affect ActualWidth/Height of the control. Try using LayoutTransform instead.

WPF WriteableBitmap drawing over loaded picture with mouse and save. Strange cursor shift

I'm trying to implement some WPF drawing sample to understand how it works. I can solve such task with C++ very quickly but I want to understand WPF means.
During the implementation of a task I faced with some strange problem: coordinates shift of mouse cursor responsible to pixels I can see on canvas.
First of all, my task is: load some picture from file; show it on Image component; allow to draw over image with mouse (like pencil tool); save changes to new file. Task is easy to implement.
Here is my code:
XAML:
<Window x:Class="MyPaint.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:MyPaint"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Background="#FF000000"
mc:Ignorable="d"
Title="Strange Paint" Height="503.542" Width="766.281" Icon="icons/paint.png">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Column="0" Grid.Row="1" Width="Auto">
<StackPanel HorizontalAlignment="Left" Width="Auto" Background="{x:Null}">
<Button x:Name="arrowButton" Width="25" Height="25" HorizontalAlignment="Left" Click="ArrowButton_Click">
<Image Source="icons/arrow.png"/>
</Button>
<Button x:Name="selectorButton" Width="25" Height="25" Click="SelectorButton_Click" HorizontalAlignment="Left">
<Image Source="icons/select_selection_tool-128.png"/>
</Button>
<Button x:Name="clearButton" Width="25" Height="25" Click="ClearButton_Click" HorizontalAlignment="Left">
<Image Source="icons/clear.png"/>
</Button>
<Button x:Name="pencilButton" Width="25" Height="25" Click="PencilButton_Click" HorizontalAlignment="Left">
<Image Source="icons/pencil.png"/>
</Button>
<Button x:Name="fillButton" Width="25" Height="25" Click="FillButton_Click" HorizontalAlignment="Left">
<Image Source="icons/fill.png"/>
</Button>
<xctk:ColorPicker Width="50" Name="ClrPcker_Foreground" SelectedColorChanged="ClrPcker_Foreground_SelectedColorChanged">
</xctk:ColorPicker>
</StackPanel>
</Grid>
<Grid x:Name="drawingCanvas" Grid.Column="1" Grid.Row="1" MouseMove="paintImageCanvas_MouseMove" MouseLeave="PaintImageCanvas_MouseLeave" MouseLeftButtonUp="PaintImageCanvas_MouseLeftButtonUp">
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="scrCanvas" Width="{Binding ActualWidth, ElementName=paintImageCanvas}" Height="{Binding ActualHeight, ElementName=paintImageCanvas}" >
<Image x:Name="paintImageCanvas" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="UniformToFill" MouseDown="paintImageCanvas_MouseDown" MouseMove="paintImageCanvas_MouseMove">
</Image>
<Rectangle x:Name="Rect" Stroke="DarkOrange" Visibility="Collapsed" Fill="#77EEEEEE"></Rectangle>
</Canvas>
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="0">
<Menu IsMainMenu="True" DockPanel.Dock="Top" Background="#FF000000">
<MenuItem Header="_File" Foreground="White" Background="#FF000000">
<MenuItem x:Name="newMenuItem" Header="_New" Background="#FF000000" Click="NewMenuItem_Click"/>
<MenuItem x:Name="openMenuItem" Header="_Open" Click="openMenuItem_Click" Background="#FF000000"/>
<MenuItem Header="_Close" Background="#FF000000"/>
<MenuItem Header="_Save" Background="#FF000000" Click="MenuItem_Click"/>
<MenuItem x:Name="exitMenuItem" Header="_Exit" Click="exitMenuItem_Click" Background="#FF000000"/>
</MenuItem>
</Menu>
<StackPanel></StackPanel>
</StackPanel>
</Grid>
Implementation of window class:
Class members:
Point currentPoint = new Point();
ToolBoxTypes currentSelectedTool = ToolBoxTypes.Unknown;
Color foregroundColor = Brushes.Black.Color;
WriteableBitmap imageWriteableBitmap;
Constructor and initialization (init white canvas 1024x768):
public MainWindow()
{
InitializeComponent();
ClrPcker_Foreground.SelectedColor = foregroundColor;
imageWriteableBitmap = BitmapFactory.New(1024, 768);
paintImageCanvas.Source = imageWriteableBitmap;
imageWriteableBitmap.Clear(Colors.White);
int i = 0;
}
Mouse down event (here I get the first point):
private void paintImageCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
currentPoint = e.GetPosition(paintImageCanvas);
}
if (currentSelectedTool == ToolBoxTypes.PencilTool)
{
}
}
Mouse move event (draw on canvas if pressed):
private void paintImageCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if(currentSelectedTool == ToolBoxTypes.PencilTool)
{
int x1 = Convert.ToInt32(currentPoint.X);
int y1 = Convert.ToInt32(currentPoint.Y);
int x2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).X);
int y2 = Convert.ToInt32(e.GetPosition(paintImageCanvas).Y);
Console.WriteLine("Mouse X: " + x2 + " Mouse Y: " + y2);
imageWriteableBitmap.DrawLine( x1, y1, x2, y2, foregroundColor );
currentPoint = e.GetPosition(paintImageCanvas);
}
}
}
Ok. That's easy code.
And now, two usecases:
On start and init I can see white canvas and can draw with mouse without any problem (cursor follows pixels):
usecase 1
I loaded picture (size is 700x600) and got a problem, cursor has different place (can see a shift):
usecase 2
I think that problem is that canvas (Image) has different side than actual picture's side. I'm not sure.
Could you help me please to understand what is wrong and how to fix that?
Thanks.
Thanks to Dmitry (see comments on my question) the reason of the problem has been found: DPI of source picture.
I changed my code of picture loading and it works fine:
private void openMenuItem_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JPEG files (*.jpg)|*.jpg|PNG files (*.png)|*.png";
if (openFileDialog.ShowDialog() == true)
{
BitmapImage image = new BitmapImage(new Uri(openFileDialog.FileName));
imageWriteableBitmap = new WriteableBitmap(image);
double dpi = 96;
int width = imageWriteableBitmap.PixelWidth;
int height = imageWriteableBitmap.PixelHeight;
int stride = width * 4;
byte[] pixelData = new byte[stride * height];
imageWriteableBitmap.CopyPixels(pixelData, stride, 0);
BitmapSource bmpSource = BitmapSource.Create(width, height, dpi, dpi, PixelFormats.Bgra32, null, pixelData, stride);
imageWriteableBitmap = new WriteableBitmap(
bmpSource.PixelWidth,
bmpSource.PixelHeight,
bmpSource.DpiX, bmpSource.DpiY,
bmpSource.Format, null);
imageWriteableBitmap.WritePixels(
new Int32Rect(0, 0, bmpSource.PixelWidth, bmpSource.PixelHeight), pixelData, stride, 0);
paintImageCanvas.Source = imageWriteableBitmap;
}
}

WPF Rotate line with angle from center point

I am trying to draw lines using WPF and c# and Facing the below problem.
Imaging I have to draw a line with fixed length, I need to rotate this line with the given angle. suppose 45 degree.
But the condition is I should rotate this from center point.
I am attaching the image for clean understanding.
Can any one please help me to write a C# program.
To rotate the line by a custom angle, apply a RotateTransform to it. You can set RenderTransformOrigin property to 0.5 0.5 to rotate around center point.
<Grid Width="200" Height="200">
<Line X1="0" Y1="0" X2="1" Y2="0" Stretch="Uniform" Stroke="Blue" StrokeThickness="2" RenderTransformOrigin="0.5 0.5">
<Line.RenderTransform>
<RotateTransform Angle="45" />
</Line.RenderTransform>
</Line>
</Grid>
If the angle is fixed (e.g. always the same), you can calculate coordinates of the start and end points, and draw a diagonal line without using transform:
<Line X1="0" Y1="0" X2="200" Y2="200" Stroke="Blue" StrokeThickness="2" />
here's the idea, you need to improve it, shape it to what you need
private void Rotate()
{
while (Degrees >= 360)
Degrees -= 360;
while (Degrees <= -360)
Degrees += 360;
X1 = 0;
Y1 = 0;
var rad = Degrees * Math.PI / 180;
const int radius = 100;
var sin = Math.Sin(rad);
var cos = Math.Cos(rad);
var tan = Math.Tan(rad);
Y2 = sin * radius;
X2 = cos * radius;
}
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" >
<Label Content="Rotation" />
<TextBox Text="{Binding Degrees}" Width="50" Margin="5,5,0,5" HorizontalContentAlignment="Right" VerticalContentAlignment="Center"/>
<Label Content="°" />
<Button Content="Rotate" Margin="5" Command="{Binding RotateCommand}"/>
</StackPanel>
<Grid Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" >
<Line Stroke="Red" X1="{Binding X1}" X2="{Binding X2}" Y1="{Binding Y1}" Y2="{Binding Y2}"/>
</Grid>
</Grid>

Grid Splitter working on diagonal?

I'm trying to do a Window with a transparent square inside it, and I want to allow the user to re-size it in whatever way s/he wants. This code is working for vertical and horizontal re-size
<Window x:Class="TransparentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PloofTAS" Height="355" Width="539" Topmost="True"
ResizeMode="NoResize"
AllowsTransparency="True"
Background="Transparent"
WindowStyle="None" MouseLeftButtonDown="Window_MouseLeftButtonDown">
<Grid Name="GlobalGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="8" />
<RowDefinition Height="*" />
<RowDefinition Height="8" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Fill="Gray" Grid.Column="0" Grid.RowSpan="5" />
<Rectangle Fill="Gray" Grid.Column="4" Grid.RowSpan="5" />
<Rectangle Fill="Gray" Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="3" />
<Rectangle Fill="Gray" Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="3" />
<GridSplitter Grid.Column="2" Grid.Row="1" Height="Auto" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="1" Grid.Row="2" Height="Auto" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="3" Grid.Row="2" Height="Auto" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="2" Grid.Row="3" Height="Auto" HorizontalAlignment="Stretch" />
<Rectangle Fill="Orange" Grid.Row="1" Grid.Column="1" />
<Rectangle Fill="Orange" Grid.Row="1" Grid.Column="3" />
<Rectangle Fill="Orange" Grid.Row="3" Grid.Column="1" />
<Rectangle Fill="Orange" Grid.Row="3" Grid.Column="3" />
<Rectangle Fill="Transparent" Stroke="Red" Grid.Column="2" Grid.Row="2"/>
</Grid>
</Window>
Here is the resulting window
I'd like for the orange squares (the corners of the red/transparent square) to be able to work diagonally, or both vertical and horizontal. Is it possible?
I don't know elegant solution for this problem as GridSplitter is not possible to set up programmatically.
My solution is just dirty mouse capture and set Column and Row measures according to mouse movement.
Set style and events. Tag property store index of a row and column we gonna to change in code behind.
<Window.Resources>
<Style x:Key="DiagonalSplitterRectangle" TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="Orange"/>
<EventSetter Event="MouseDown" Handler="UIElement_OnMouseDown"/>
<EventSetter Event="MouseMove" Handler="UIElement_OnMouseMove"/>
<EventSetter Event="MouseUp" Handler="UIElement_OnMouseUp"/>
<EventSetter Event="LostMouseCapture" Handler="UIElement_OnLostMouseCapture"/>
</Style>
</Window.Resources>
<Rectangle Grid.Row="1" Grid.Column="1" Style="{StaticResource DiagonalSplitterRectangle}" Tag="0,0"/>
<Rectangle Grid.Row="1" Grid.Column="3" Style="{StaticResource DiagonalSplitterRectangle}" Tag="0,4"/>
<Rectangle Grid.Row="3" Grid.Column="1" Style="{StaticResource DiagonalSplitterRectangle}" Tag="4,0"/>
<Rectangle Grid.Row="3" Grid.Column="3" Style="{StaticResource DiagonalSplitterRectangle}" Tag="4,4"/>
Simple mouse capture events:
private bool _isMouseCaptured;
public MainWindow()
{
InitializeComponent();
}
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var uiElement = sender as UIElement;
if (uiElement == null)
return;
if (uiElement.CaptureMouse())
_isMouseCaptured = true;
}
private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
{
if (!_isMouseCaptured)
return;
var clientWindow = Content as FrameworkElement;
if (clientWindow == null)
return;
var rectangle = sender as Rectangle;
if (rectangle == null)
return;
Point position = Mouse.GetPosition(GlobalGrid); ;
if (position.X < 0 || position.Y < 0 || position.X > clientWindow.ActualWidth || position.Y > clientWindow.ActualHeight)
return;
GridUpdate(position, rectangle, clientWindow);
}
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
{
if (!_isMouseCaptured)
return;
var uiElement = sender as UIElement;
if (uiElement == null)
return;
uiElement.ReleaseMouseCapture();
}
private void UIElement_OnLostMouseCapture(object sender, MouseEventArgs e)
{
_isMouseCaptured = false;
}
Resize grid columns and rows according to value stored in rectangle.Tag property.
For correct behavior was needed to change opposite column and row measures too.
private void GridUpdate(Point position, Rectangle rectangle, FrameworkElement clientWindow)
{
var gridPosition = new GridPosition(rectangle.Tag.ToString());
var oppositeGridPosition = GetOppositeGridPosition(gridPosition);
var rowHeight = GetMeasure(gridPosition.Row, position.Y, clientWindow.ActualHeight);
var columnWidth = GetMeasure(gridPosition.Column, position.X, clientWindow.ActualWidth);
var oppositeRowHeight = GlobalGrid.RowDefinitions[oppositeGridPosition.Row].ActualHeight;
var oppositeColumnWidth = GlobalGrid.ColumnDefinitions[oppositeGridPosition.Column].ActualWidth;
GlobalGrid.RowDefinitions[gridPosition.Row].Height = new GridLength(rowHeight);
GlobalGrid.ColumnDefinitions[gridPosition.Column].Width = new GridLength(columnWidth);
GlobalGrid.RowDefinitions[oppositeGridPosition.Row].Height = new GridLength(oppositeRowHeight);
GlobalGrid.ColumnDefinitions[oppositeGridPosition.Column].Width = new GridLength(oppositeColumnWidth);
}
private GridPosition GetOppositeGridPosition(GridPosition gridPosition)
{
var row = (gridPosition.Row == 0) ? 4 : 0;
var column = (gridPosition.Column == 0) ? 4 : 0;
return new GridPosition(row, column);
}
private double GetMeasure(int gridPosition, double position, double windowMeasure)
{
return gridPosition == 0 ? position : windowMeasure - position;
}
GridPosition is structure that stores column and row index values.
public struct GridPosition
{
public int Row { get; private set; }
public int Column { get; private set; }
public GridPosition(int row, int column)
: this()
{
Row = row;
Column = column;
}
public GridPosition(string gridPostion)
: this()
{
Row = Convert.ToInt32(gridPostion.Split(',')[0]);
Column = Convert.ToInt32(gridPostion.Split(',')[1]);
}
}

Rendering images fails when image over 40,000 wide

I am trying to create a time scale like in Adobe Premiere like this:
But, I have to go to down to 0.01 second increments.
My timeline control looks like:
UPDATE:
I have used #Sten Petrov suggestion and used a VisualBrush.
But now I am stuck on how to implement the Label for the Seconds.
My new code (containing control can change):
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="680.839">
<Grid Background="Black">
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible" >
<ScrollViewer.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid SnapsToDevicePixels="False" UseLayoutRounding="True">
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,5,30" ViewportUnits="Absolute" Viewbox="0,0,5,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Coral" StrokeThickness="2" X1="0" X2="0" Y1="25" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid Margin="50,0,0,0" SnapsToDevicePixels="False" UseLayoutRounding="True">
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,50,30" ViewportUnits="Absolute" Viewbox="0,0,50,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Red" StrokeThickness="2" X1="0" X2="0" Y1="20" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" >
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,500,30" ViewportUnits="Absolute" Viewbox="0,0,500,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid HorizontalAlignment="Left" Width="500" UseLayoutRounding="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="21*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="1" Height="9" Content=".100" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="2" Height="9" Content=".200" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="3" Height="9" Content=".300" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="4" Height="9" Content=".400" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="5" Height="9" Content=".500" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="6" Height="9" Content=".600" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="7" Height="9" Content=".700" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="8" Height="9" Content=".800" Foreground="White"/>
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="9" Height="9" Content=".900" Foreground="White"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" Margin="500,0,0,0" >
<Grid.Background>
<VisualBrush TileMode="Tile" Viewport="0,0,500,30" ViewportUnits="Absolute" Viewbox="0,0,500,30" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Line Stroke="Blue" StrokeThickness="2" X1="0" X2="0" Y1="10" Y2="30" UseLayoutRounding="True" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
</Grid>
<Grid SnapsToDevicePixels="False" Height="30" UseLayoutRounding="True" Margin="491,0,0,0" >
<Grid.RowDefinitions>
<RowDefinition Height="7"/>
<RowDefinition Height="23"/>
</Grid.RowDefinitions>
<!--Need something here-->
<Label Grid.Row="0" FontFamily="Tahoma" FontSize="8" Padding="0" VerticalAlignment="Bottom" Grid.Column="0" Height="9" Content="00:00" Foreground="White"/>
</Grid>
</Grid>
</DataTemplate>
</ScrollViewer.ContentTemplate>
</ScrollViewer>
</Grid>
/UPDATE
I go to 0.01 seconds per line, so for a 10 minute timeline I am looking at drawing 60000 lines + 6000 labels.
I asked a prior question on this before: 10000's+ UI elements, bind or draw?
Originally I was drawing lines directly on a Canvas.
Then I went to using a VisualHost because it is supposed to be lighter weight.
Well it isn't light enough.
I have a MediaElement that plays a video and the Timeline scrolls in sync with the video position. A ScrollViewer wraps my Timeline and does .ScrollToHorizontalOffset about every 10ms.
If my Timeline was over something like 3 minutes the video shutters.
I assume this is because a VisualHost still has all the Framework Elements in it and the scrolling causes them to be re-validated.
So now I am trying to generate a Image to display, I think that should be lighter yet.
Am I wrong in this assumption?
Now I am facing issues with making the Timeline into an Image.
I could not Render an entire Timeline to a Image so I am 'Chunking' it. I was hitting Exceptions about Image size being to big.
On to my code:
This is my main entry point.
public void RenderHeaderPicture()
{
const int ChunkSize = 5000;
var bitmapFrames = new List<BitmapFrame>();
// generates X number of DrawingVisual's based on ChunkSize
List<DrawingVisual> visuals = generateHeaderVisualChunks(
AppViewModel.TimelineViewModel.HeaderWidth, ChunkSize, TimelineViewModel.ViewLevel.Level1);
for (var i = 0; i < visuals.Count; i++)
{
var renderTargetBitmap = new RenderTargetBitmap(ChunkSize, 30, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(visuals[i]);
//test to make sure image good
saveHeaderSegmentAsPng(string.Format("headerSeg{0}.png", i), renderTargetBitmap);
bitmapFrames.Add(BitmapFrame.Create(renderTargetBitmap));
}
// put the frames back together now
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
for (int i = 0; i < bitmapFrames.Count; i++)
{
drawingContext.DrawImage(bitmapFrames[i], new Rect(i * ChunkSize, 0, bitmapFrames[i].PixelWidth, 30));
}
drawingContext.Close();
}
var newBmp = new RenderTargetBitmap(AppViewModel.TimelineViewModel.HeaderWidth, 30, 96, 96, PixelFormats.Pbgra32);
newBmp.Render(drawingVisual);
AppViewModel.TimelineViewModel.HeaderImageSource = newBmp;
}
Here is the code that creates the DrawingVisual's
private List<DrawingVisual> generateHeaderVisualChunks(int width, int chunkSize, TimelineViewModel.ViewLevel level)
{
var ret = new List<DrawingVisual>();
var currentTime = new TimeSpan(0, 0, 0, 0, 0);
var timeStep = new TimeSpan(0, 0, 0, 0, (int)level);
var currentLine = 0;
const double DistanceBetweenLines = 5;
const int TenthOfSecondLine = 10;
const int SecondLine = 100;
int iterations = (width / chunkSize);
int remainder = width % chunkSize; //not doing anything with yet
var grayBrush = new SolidColorBrush(Color.FromRgb(192, 192, 192));
var grayPen = new Pen(grayBrush, 2);
var whitePen = new Pen(Brushes.Purple, 2);
grayBrush.Freeze();
grayPen.Freeze();
whitePen.Freeze();
for (int i = 0; i < iterations; i++)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
double currentX = 0;
if (i > 0)
{
currentLine--;
currentTime -= timeStep;
}
while (currentX <= chunkSize)
{
if (((currentLine % SecondLine) == 0) && currentLine != 0)
{
dc.DrawLine(whitePen, new Point(currentX, 30), new Point(currentX, 15));
FormattedText text = null;
double tempX = currentX;
switch (level)
{
case TimelineViewModel.ViewLevel.Level1:
text = new FormattedText(
currentTime.ToString(#"hh\:mm\:ss\.fff"),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"),
8,
grayBrush);
break;
}
dc.DrawText(text, new Point((tempX - 22), 0));
}
else if ((((currentLine % TenthOfSecondLine) == 0) && currentLine != 0)
&& (currentLine % SecondLine) != 0)
{
dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 20));
FormattedText text = null;
switch (level)
{
case TimelineViewModel.ViewLevel.Level1:
text = new FormattedText(
string.Format(".{0}", currentTime.Milliseconds),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"),
8,
grayBrush);
break;
}
dc.DrawText(text, new Point((currentX - 8), 8));
}
else
{
dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 25));
}
currentX += DistanceBetweenLines;
currentLine++;
currentTime += timeStep;
}
}
ret.Add(visual);
}
return ret;
}
Save png segment:
private static void saveHeaderSegmentAsPng(string fileName, RenderTargetBitmap renderTargetBitmap)
{
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (var fileStream = new FileStream(fileName, FileMode.Create))
{
pngBitmapEncoder.Save(fileStream);
fileStream.Flush();
fileStream.Close();
}
}
All of my png segments are rendered correctly in their separate files.
And my Timeline is rendered correctly until I go over 1:20 then things break.
See:
It's like the Image is smeared or something.
Anyone know what is going on with this?
Thanks
I'm waiting for the day when my living room will have a TV with horizontal resolution that will require something like your approach.
Scrap this whole piece of code you just demoed here, it will never become usable and maintainable, you could only manage to squeeze one of these out of it.
Then learn about VisualBrush, there are plenty of tutorials out there, it can repeat your visual template, no need of PNGs and it will scale better when screen resolution changes (up to 40001px wide)
For the numbers that appear above the marks there are a million different approaches, some of them can be combined with the visual brush mentioned above, such as a user control that represents your timeline unit (the space between two larger marks). Now place several of them in a grid (stackpanel, canvas... as you wish) and adjust (dynamically) their offset and labels - all of a sudden you can represent an infinite timeline with 10 controls on the screen.

Categories