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>
Related
Hello I need a solution of special shape of starting and ending line. I use Line Property . I want a solution like this ..
You can see there is "+" shape of this Line property in starting and ending . How to do in XAML and in my code . Here is my code
<Canvas Margin="10" IsEnabled="{Binding IsEnableCanvas}" Visibility="{Binding Path=ISCanvasVisible, Converter={StaticResource Converter}}">
<Image Source="{Binding DIIMGFINAL}" cal:Message.Attach="[Event MouseDown] = [Action MDownCalCulateDistance($source, $eventArgs)];
[Event MouseUp] = [Action MUpCalCulateDistance($source, $eventArgs)];
[Event MouseMove] = [Action MMoveCalCulateDistance($source, $eventArgs)]" Stretch="Uniform" />
<Line Visibility="{Binding Path=ISLineDistanceVisible, Converter={StaticResource Converter}}" IsHitTestVisible="False" X1="{Binding FirstPoint.X}" Y1="{Binding FirstPoint.Y}"
X2="{Binding SecondPoint.X}" Y2="{Binding SecondPoint.Y}" StrokeStartLineCap="Triangle" StrokeEndLineCap="Triangle"
Stroke="Red" StrokeThickness="2">
</Line>
<TextBlock Canvas.Left="{Binding TxtblckPoint_First_Left.X}" Canvas.Top="{Binding TxtblckPoint_Second_Left.Y}" Text="{Binding Path=DisTanceInMM, Mode=OneWay}" FontSize="20" Foreground="Yellow"></TextBlock></Canvas>
As you can see I use StrokeStartLineCap and StrokeEndLineCap Triangle but it is not what I wanted . It can visible if I use StrokeThickness a greater value . But my requirement is not that much thick . Here is My C# code
if (_firstPoint.X == 0 && _firstPoint.Y == 0)
{
System.Windows.Point px1 = e.GetPosition((System.Windows.Controls.Image)sender);
_firstPoint = px1;
}
else if((_firstPoint.X != 0 && _firstPoint.Y != 0) && (_secondPoint.X == 0 && _secondPoint.Y == 0)){
System.Windows.Point px2 = e.GetPosition((System.Windows.Controls.Image)sender);
_secondPoint = px2;
var geometry = new FrameGeometry(DicomDataSet);
var patientCoord1 = geometry.TransformImagePointToPatient(new Point2(Convert.ToInt32(_firstPoint.X), Convert.ToInt32(_firstPoint.Y)));
var patientCoord2 = geometry.TransformImagePointToPatient(new Point2(Convert.ToInt32(_secondPoint.X), Convert.ToInt32(_secondPoint.Y)));
FirstPoint = _firstPoint;
SecondPoint = _secondPoint;}
What I have to do to show the special shape of Starting and Ending Point. Thanks in advance.
Place two Path elements with whatever shape you need at the endpoints:
<Path Stroke="Red" Data="M-5,0 L5,0 M0,-5 L0,5"
Canvas.Left="{Binding FirstPoint.X}"
Canvas.Top="{Binding FirstPoint.Y}"/>
<Path Stroke="Red"
Canvas.Left="{Binding SecondPoint.X}"
Canvas.Top="{Binding SecondPoint.Y}">
<Path.Data>
<GeometryGroup>
<LineGeometry StartPoint="-5,0" EndPoint="5,0"/>
<LineGeometry StartPoint="0,-5" EndPoint="0,5"/>
</GeometryGroup>
</Path.Data>
</Path>
So im trying to place an Ellipse (named Dot) on a random location inside a grid.
But it only spawns on 1/4th (lower right) of the grid instead of the complete grid.
Example: When i put the margin of the Dot to Dot.Margin = new Thickness(0, 0, 0, 0); the Dot will spawn in the center of the screen. When i change it to Dot.Margin = new Thickness(200, 0, 0, 0); it will have a very small offset to the right but not even close to 200 pixels.
Outcome after creating alot of circles without removing them:
http://i.imgur.com/3XCMYyC.png
The red rectangle is the spawn area.
C#
//Gives Dot a position
public void placeDot()
{
//Give Dot random position
// The farthest left the dot can be
double minLeft = 0;
// The farthest right the dot can be without it going off the screen
double maxLeft = spawnArea.ActualWidth - Dot.Width;
// The farthest up the dot can be
double minTop = 0;
// The farthest down the dot can be without it going off the screen
double maxTop = spawnArea.ActualHeight - Dot.Height;
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
//createEllipse method, used for creating new Dots
public void createEllipse()
{
spawnArea.Children.Remove(Dot);
//Create new Dot
DotImg.ImageSource =
new BitmapImage(new Uri(#"Images/hitcircle.png", UriKind.Relative));
Dot = new Ellipse() { Width = hitcircleSettingPath, Height = hitcircleSettingPath, Fill = DotImg, };
//Activates placeDot() method to give the Dot a random location
placeDot();
//Add Dot to the game area
spawnArea.Children.Add(Dot);
}
XAML This is the complete XAML i think there might be something wrong with the XAML but below is a shorter snippet where it does work how i want it but without the other stuff.
<Window x:Name="Grid" x:Class="ReactieSnelheid_Game.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:ReactieSnelheid_Game"
mc:Ignorable="d"
Title="Dot Program" Height="768" Width="1366" WindowStartupLocation="CenterScreen" Cursor="Pen" Icon="Images/icon.ico" ResizeMode="NoResize" WindowState="Maximized" WindowStyle="None" Background="Black" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="{x:Null}" KeyDown="Grid_KeyDown">
<Grid x:Name="backgroundImageGrid" Margin="0,0,0,0" MouseDown="LayoutRoot_MouseDown">
<Grid.Background>
<ImageBrush x:Name="backgroundBrush" ImageSource="Images/background.png" Opacity="0.25"/>
</Grid.Background>
<Grid x:Name="gameWrapper">
<Grid.Background>
<SolidColorBrush Color="Black" Opacity="0.25"/>
</Grid.Background>
<Label x:Name="reactionTime" Content="1000ms" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" FontSize="40" FontFamily="PMingLiU-ExtB" Foreground="White"/>
<Label x:Name="circleSize" Content="150x150" HorizontalAlignment="Left" Margin="10,73,0,0" VerticalAlignment="Top" FontSize="26.667" FontFamily="PMingLiU-ExtB" Foreground="White" Background="{x:Null}"/>
<Label x:Name="dotCount" Content="000000" HorizontalAlignment="Left" Margin="28,0,0,-1" Foreground="White" FontSize="80" RenderTransformOrigin="0.5,0.5" FontFamily="PMingLiU-ExtB" VerticalAlignment="Bottom" Background="{x:Null}">
<Label.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="-11.056"/>
<RotateTransform/>
<TranslateTransform X="-8.305"/>
</TransformGroup>
</Label.RenderTransform>
</Label>
<Label x:Name="scoreCount" Content="9999999999999999" Margin="0,0,10,10" Foreground="White" FontSize="80" FontFamily="PMingLiU-ExtB" HorizontalAlignment="Right" Height="106" VerticalAlignment="Bottom"/>
<Rectangle x:Name="startTimerBtn" RenderTransformOrigin="0.39,-0.75" Margin="144,10,0,0" Width="128" Height="64" HorizontalAlignment="Left" VerticalAlignment="Top" MouseDown="startTimerBtn_MouseDown">
<Rectangle.Fill>
<ImageBrush ImageSource="Images/starttimer.png"/>
</Rectangle.Fill>
</Rectangle>
<Grid x:Name="spawnArea" Margin="0,115,0,121" Background="Red"/>
</Grid>
<Grid x:Name="pauseScreen">
<Grid.Background>
<SolidColorBrush Color="#FF81D650" Opacity="0.25"/>
</Grid.Background>
<Grid x:Name="pauseScreenBtns" Margin="389,153,389,152" HorizontalAlignment="Center" VerticalAlignment="Center">
<Rectangle x:Name="Startbtn" Height="332" Width="332" MouseDown="Startbtn_MouseDown" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="129,0,127,76">
<Rectangle.Fill>
<ImageBrush ImageSource="Images/Start.png"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="settingsBtn" MouseDown="settingsBtn_MouseDown" Margin="-5,189,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="129" Height="63">
<Rectangle.Fill>
<ImageBrush ImageSource="Images/settings.png"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="resetBtn" MouseDown="resetBtn_MouseDown" Margin="0,190,-6,0" Width="128" Height="62" HorizontalAlignment="Right" VerticalAlignment="Top">
<Rectangle.Fill>
<ImageBrush ImageSource="Images/reset.png"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="quitBtn" RenderTransformOrigin="0.39,-0.75" Margin="232,399,0,0" Width="129" Height="64" HorizontalAlignment="Left" VerticalAlignment="Top" MouseDown="quitBtn_MouseDown">
<Rectangle.Fill>
<ImageBrush ImageSource="Images/quit.png"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Grid>
</Grid>
</Window>
Shorter XAML
<Window x:Class="TESTPROJECTEN.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:TESTPROJECTEN"
mc:Ignorable="d"
Title="MainWindow" Height="768" Width="1366">
<Grid x:Name="gameWrapper">
<Grid x:Name="spawnArea"/>
</Grid>
</Window>
Ellipse has Alignment = Stretch by default. But you set a fixed Width and Height so it looks like ellipse is centered. Try set location relative to top left corner
Dot = new Ellipse() { Width = hitcircleSettingPath, Height = hitcircleSettingPath, Fill = DotImg, };
Dot.HorizontalAlignment = HorizontalAlignment.Left;
Dot.VerticalAlignment = VerticalAlignment.Top;
it will have a very small offset to the right but not even close to
200 pixels.
First of all, as far as I remember, WPF doesn't use those numbers as pixels, but as some internal values. I can't remember the name of those units, but bottom line, they are not pixels.
Now for your actual problem:
Wpf grid places all his child controls by default in the center, so when you are putting the ellipse inside the grid and set a margin 200, it sets the margin from the center.
With that in mind, you can change your random algorithm to make the bubbles appear all over the grid:
//Gives Dot a position
public void placeDot()
{
//Give Dot random position
double halfSide = (spawnArea.ActualWidth - Dot.Width) / 2;
// The farthest left the dot can be
double minLeft = -(halfSide - (Dot.ActualWidth / 2));
// The farthest right the dot can be without it going off the screen
double maxLeft = halfSide - (Dot.ActualWidth / 2);
// The farthest up the dot can be
double minTop = -(halfSide - (Dot.ActualHeight / 2));
// The farthest down the dot can be without it going off the screen
double maxTop = halfSide - (Dot.ActualHeight / 2);
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
I hope I didn't miss anything. I didn't run this code, so it might need adjustments, but this is the main line to follow.
Happy Coding! :)
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.
I have created Ellipse in XAML.
Here is the code :
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Ellipse Width="400" Stroke="DodgerBlue" Height="400" StrokeThickness="75" Fill="Transparent">
</Ellipse>
</Grid>
Say the Ellipse is 100% if its 20% the blue color should fill only till that and also display the percentage text in the center (empty area) of ellipse.
EDIT
I have add text to display in center.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Ellipse Width="400" Stroke="DodgerBlue" Height="400" StrokeThickness="75" Fill="Transparent">
</Ellipse>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="20"
FontSize="125"/>
</Grid>
EDIT 2
Here is what how it looks like i am trying to acheive:
here the orange color with the 20% fill.
You can use an arc control preset in the assembly Microsoft.Expression.Drawing
It has properties like StartAngle and EndAngle which could be well manipulated accordingly.
<es:Arc x:Name="arc" ArcThickness="3" ArcThicknessUnit="Pixel" EndAngle="360" Fill="Black" Height="270" Canvas.Left="101.94" Stroke="Black" StartAngle="0" UseLayoutRounding="False" Width="269.941" Canvas.Top="12" />
Now what you could do using this control is : Just take two similar arcs One superimposing the other,
color the below one(1st arc) with Blue and give start and end angle properties to the red color arc(2nd arc) which would make your layout look like the way it is mentioned in design two.
Raw Usage:
<Canvas x:Name="canvas1" Margin="0,10,0,0" Height="300" Width="480" HorizontalAlignment="Center">
<es:Arc x:Name="arc" ArcThickness="3" ArcThicknessUnit="Pixel" Fill="Black" Height="100" Canvas.Left="0" Stroke="Blue" UseLayoutRounding="False" Width="100" Canvas.Top="0"/>
</Canvas>
<es:Arc x:Name="arc" ArcThickness="3" EndAngle="120" StartAngle="0" ArcThicknessUnit="Pixel" Fill="Blue" Height="100" Canvas.Left="0" Stroke="Blue" UseLayoutRounding="False" Width="100" Canvas.Top="0"/>
</Canvas>
Check this link as well
User control version would consist of two parts: XAML + code-behind.
XAML part:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Project.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d"
d:DesignHeight="40"
d:DesignWidth="40">
<Grid Width="40" Height="40">
<Ellipse StrokeThickness="3" Stroke="#FF89CE25"/>
<Path Stroke="Red" StrokeThickness="2" x:Name="arcPath">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="20,1">
<ArcSegment x:Name="myArc" SweepDirection="Clockwise" IsLargeArc="True" Point="20,1"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
Code-behind file (short version, no fluff):
public sealed partial class MyCustomControl : UserControl
{
public double ProgressValue
{
get { return (double)GetValue(ProgressValueProperty); }
set { SetValue(ProgressValueProperty, value); }
}
// Using a DependencyProperty as the backing store for ProgressValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProgressValueProperty =
DependencyProperty.Register("ProgressValue", typeof(double), typeof(MyCustomControl), new PropertyMetadata(0.0, OnProgressValueChanged));
private static void OnProgressValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
MyCustomControl circularProgressBar = d as MyCustomControl;
if (circularProgressBar != null)
{
double r = 19;
double x0 = 20;
double y0 = 20;
circularProgressBar.myArc.Size = new Size(19, 19);
double angle = 90 - (double)e.NewValue / 100 * 360;
double radAngle = angle * (PI / 180);
double x = x0 + r * Cos(radAngle);
double y = y0 - r * Sin(radAngle);
if (circularProgressBar.myArc != null)
{
circularProgressBar.myArc.IsLargeArc = ((double)e.NewValue >= 50);
circularProgressBar.myArc.Point = new Point(x, y);
}
}
}
public MyCustomControl()
{
this.InitializeComponent();
}
}
Now, you can throw your CustomControl into any place in your XAML and bind the ProgressValue property to the respective data source. The arc would redraw itself and will fill the Ellipse shape proportionally to the value (a value from 0-100).
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.