Special shape of Line starting and ending point in wpf C# - c#

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>

Related

Copy wpf imageSource to another wpf imageSource

I want to copy imageSource to another imageSource. When A window contains image is closed, then I want to make image in A will be copied to B window image and then open B window.
I tried to test with below code. but it wasn`t work.
ADDED :
I upload more code about relevant image code. but im sorry that i cant upload full source code but If you ask, I will edit the related source and upload it.
A a = new A();
B b;
a.Closing += delegate {
b = new B();
b.img.Source = a.img.Source;
b.Show();
};
a.Show();
<!-- a.img xml -->
<Grid x:Name="ImageGrid" Margin="0,36,10,10">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform"
ScaleX="{Binding ZoomRate}" ScaleY="{Binding ZoomRate}"/>
<RotateTransform/>
</TransformGroup>
</Grid.LayoutTransform>
<Image Name="img" HorizontalAlignment="Left" VerticalAlignment="Top"
Source="{Binding BitmapSource, Mode=TwoWay}"/>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top"
Name="canvas2"
Background="Transparent"
Width="{Binding Path=ActualWidth, ElementName=imgBefore2}"
Height="{Binding Path=ActualHeight, ElementName=imgBefore2}"
MouseLeftButtonDown="Canvas2_MouseLeftButtonDown"
MouseMove="Canvas2_MouseMove"
MouseLeftButtonUp="Canvas2_MouseLeftButtonUp"
MouseRightButtonDown="Canvas2_MouseRightButtonDown">
</Canvas>
</Grid>
<!-- b.img xml -->
<Grid x:Name="ImageGrid"
Background="Transparent"
Width="{Binding MaxWidth, ElementName=ScrollViewer}"
Height="{Binding MaxHeight, ElementName=ScrollViewer}"
MouseMove="Canvas_MouseMove"
MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
MouseLeftButtonUp="Canvas_MouseLeftButtonUp"
MouseWheel="Canvas_MouseWheel">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="ScaleTransform" ScaleX="{Binding ZoomRate}" ScaleY="{Binding ZoomRate}"/>
<RotateTransform/>
</TransformGroup>
</Grid.LayoutTransform>
<Image x:Name="img" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top"
x:Name="canvas"
Background="Transparent"
Width="{Binding ActualWidth, ElementName=imgBefore}"
Height="{Binding ActualHeight, ElementName=imgBefore}"/>
</Grid>
public Mat src;
public BitmapSource BitmapSource {
get
{
return bitmapSource;
}
set
{
bitmapSource = value;
OnPropertyChanged("bitmapSource");
} // ViewModel Code
}
private void LoadImage(System.Drawing.Bitmap bm)
{
src = OpenCvSharp.Extensions.BitmapConverter.ToMat(bm);
OriginWidth = src.Width;
OriginHeight = src.Height;
BitmapSource = OpenCvSharp.Extensions.BitmapSourceConverter.ToBitmapSource(src);
} // Load Image for a.img

Elements loaded via XamlReader() and from MainWindow.Xaml behave differently?

I have the following Viewbox in MainWindow.Xaml:
<Viewbox x:Name="R1C1Viewbox" Grid.Row="1" Grid.Column="2"
Grid.ColumnSpan="1" Grid.RowSpan="1">
<Border x:Name="R1C1Border" BorderBrush="White" CornerRadius="3"
BorderThickness="2" Background="White"
Height="40" Width="40" Margin="0,3,3,0">
<Grid x:Name="R1C1Grid">
<TextBlock x:Name="R1C1TextBlock1" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Arial" FontWeight="Bold" Text="+" FontSize="22"></TextBlock>
<Polygon x:Name="R1C1LED"
Points="0,0 15,0 0,15"
Stroke="#FFED1C24"
StrokeThickness="1">
<Polygon.Fill>
<RadialGradientBrush>
<GradientStop Color="White" Offset="2.5"/>
<GradientStop Color="Black"/>
</RadialGradientBrush>
</Polygon.Fill>
</Polygon>
<Button x:Name="R1C1Button" Background="Transparent"
BorderBrush="Transparent">
</Button>
</Grid>
</Border>
</Viewbox>
In my code behind, I define 2 RadialGradientBrushes for the Polygon.Fill property:
public static RadialGradientBrush ledOn = new RadialGradientBrush();
public static RadialGradientBrush ledOff = new RadialGradientBrush();
ledOn.GradientStops.Add(new GradientStop(Colors.Red, .8));
ledOn.GradientStops.Add(new GradientStop(Colors.White, 0));
ledOff.GradientStops.Add(new GradientStop(Colors.White, 2.5));
ledOff.GradientStops.Add(new GradientStop(Colors.Black, 0));
If I load the viewbox only from MainWindow.xaml, I can use:
R1C1LED.Fill = ledOn;
or
R1C1LED.Fill = ledOff;
and the fill changes as expected. If I load the exact same xaml from a file using XamlReader(), the Viewbox displays exactly as expected but using the code behind to change the fill as above doesn't change the fill and no errors are generated.
Example code:
private void OnTest(object sender, RoutedEventArgs e)
{
var xaml =
#"<Window
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'
mc:Ignorable='d'
Title='WPF App' Height='450' Width='800'>
<StackPanel Orientation='Vertical'>
<Button x:Name='R1C1LED' Content='Load Data' />
</StackPanel>
</Window>";
var win = (Window)XamlReader.Parse(xaml);
var button = (Button)win.FindName("R1C1LED");
button.Background = Brushes.Red;
button.Click += (obj, args) => { MessageBox.Show("Hi!"); };
win.Show();
}

How can i plot a collection of data points on a canvas using data bindings?

I have a collection of data points which store an X and a Y value along with two coordinates: The pixel location of the point itself and of the next point.
I then have an ItemsControl which is bound to the collection and draws a line connecting the current point to the next point forming a line chart of all the data points stored in the collection.
<ItemsControl x:Name="GraphCanvas"
ItemsSource="{Binding LineChartData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Canvas.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Canvas.Resources>
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<GeometryGroup>
<PathGeometry>
<PathFigure StartPoint="{Binding Source={StaticResource proxy}, Path=Data.CurrentPoint}">
<PathFigure.Segments>
<LineSegment Point="{Binding Source={StaticResource proxy}, Path=Data.NextPoint}"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It all works alright however I want to know if there is a better way to do this as when I have to resize my control i have to loop over each of the datapoints and re calculate their current point and next point like so:
Point currentPoint;
Point nextPoint = getCanvasPoint(lineChartData[0]);
for (int i = 1; i < lineChartData.Count; i++)
{
var dataPoint = lineChartData[i];
currentPoint = nextPoint;
nextPoint = getCanvasPoint(dataPoint);
dataPoint.CurrentPoint = currentPoint;
dataPoint.NextPoint = nextPoint;
}
Which is a rather slow process and makes the resizing very jumpy. I would like to know if there is a better way for me to bind a list of X & Y values to an itemscontrol so that i can plot them onscreen.
This is how it looks:
I have been using MultiBinding for that.
In the view, I bind to:
<Grid x:Name="root" RenderTransformOrigin="0.5 0.5">
<Grid.RenderTransform>
<ScaleTransform ScaleY="-1"/>
</Grid.RenderTransform>
<Path Stroke="Brown" StrokeThickness="0.5"
Stretch="Fill"
StrokeEndLineCap="Round" StrokeLineJoin="Round">
<Path.Data>
<MultiBinding Converter="{StaticResource SinalToGeometryConverter}">
<Binding ElementName="root" Path="ActualWidth"/>
<Binding ElementName="root" Path="ActualHeight"/>
<Binding Path="Samples"/>
</MultiBinding>
</Path.Data>
</Path>
</Grid>
Then in the converter I generate a Path, more or less like this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == null) &&
values.Any(v => v == DependencyProperty.UnsetValue))
return null;
double viewportWidth = (double)values[0];
double viewportHeight = (double)values[1];
IEnumerable<int> samples= values[2] as IEnumerable<int>;
var sb = new StringBuilder("M");
int x_coord = 0; // could be taken from sample if available
foreach (var y_coord in samples)
sb.AppendFormat(" {0} {1}", x_coord++, y_coord);
var result = Geometry.Parse(sb.ToString());
return result;
}
Then, each time one of the bound things change, due to datasource change or viewport change, you get a brand new Path. This is relatively quick if you have less datapoints than your plotterControl.ActualWidth.
I used Clemens comment to build my solution here is how i did it:
I bound the Points property of a PolyLineSegment to a collection of points in my ViewModel. Then i used a transform group to scale and translate the path to fit the axis.
Here is the code:
<Canvas Background="Transparent" Grid.Row="1" Grid.Column="2" x:Name="GraphCanvas">
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Transform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding xScale}" ScaleY="{Binding yScale}"/>
<TranslateTransform Y="{Binding yAxisTranslation}"/>
</TransformGroup>
</PathGeometry.Transform>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="{Binding FirstPoint}">
<PathFigure.Segments>
<PathSegmentCollection>
<PolyLineSegment Points="{Binding Points}"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Black" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,0">
<LineSegment Point="{Binding OriginPoint}"/>
<LineSegment Point="{Binding xAxisEndPoint}"/>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
I really like this solution and it seems a considerable amount faster then my original code especially when resizing.

How to create WPF layout similar to Google Images

I'm trying to create a user interface which mimics the behavior of google images in that when a tile is clicked, the image in a "row" below the row the image is on, without causing the remaining elements in the row to move.
This is as far as I've gotten. The following user control can be added to a WrapPanel, when the user clicks on the first StackPanel, the PdfViewerWrapperGrid should appear:
<UserControl x:Class="APDesktop.Controls.PdfAttachment"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
mc:Ignorable="d"
d:DesignHeight="395" d:DesignWidth="503">
<Grid Background="{StaticResource WindowBackground}">
<StackPanel Width="100" Height="100" Margin="5" MouseUp="StackPanel_MouseUp" HorizontalAlignment="Left" VerticalAlignment="Top">
<Border BorderBrush="{StaticResource ResourceKey=ButtonBorderPressed}" Height="100" Width="100" BorderThickness="1">
<Grid>
<mui:ModernButton x:Name="DeleteButton" Width="20" IconData="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z " Margin="39,5,5,0" HorizontalAlignment="Right" VerticalAlignment="Top" Click="DeleteButton_Click"/>
<Grid Margin="20" Height="50" VerticalAlignment="Top">
<Grid.Background>
<VisualBrush Stretch="Uniform" Visual="{StaticResource PDFIcon}"/>
</Grid.Background>
</Grid>
<TextBlock x:Name="FileNameTextBlock" TextTrimming="CharacterEllipsis" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontSize="16" Foreground="{StaticResource ResourceKey=ButtonText}" Text="{Binding DisplayName}"></TextBlock>
</Grid>
</Border>
</StackPanel>
<Grid x:Name="PdfViewerWrapperGrid" Visibility="Visible">
<Polygon Points="55,110 35,125, 75,125" Stroke="{StaticResource ScrollBarBackground}" Fill="{StaticResource ScrollBarBackground}" />
<StackPanel x:Name="PdfViewerOuterStackPanel" Margin="30,125,30,0" Background="{StaticResource ScrollBarBackground}">
<Grid Margin="0,15,0,0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*"/>
<ColumnDefinition Width="0.25*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" MaxWidth="150" TextTrimming="CharacterEllipsis" Text="{Binding DisplayName}" Grid.ColumnSpan="2" FontWeight="Bold" FontSize="18"></TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<mui:ModernButton x:Name="SaveToSpecialFolderButton" ToolTip="Save to My Documents folder" IconData="F1 M 25,52L 51,52L 51,57L 25,57L 25,52 Z M 35,16L 41,16L 41,36.5L 49,27L 49,36.5L 38,49L 27,36.5L 27,27L 35,36.5L 35,16 Z " HorizontalAlignment="Right" Margin="7,0" Click="SaveToSpecialFolderButton_Click" ></mui:ModernButton>
<mui:ModernButton x:Name="SaveAnywhereButton" ToolTip="Save anywhere" IconData="F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z " HorizontalAlignment="Right" Margin="7,0" Click="SaveAnywhereButton_Click" ></mui:ModernButton>
<mui:ModernButton x:Name="CloseViewerButton" ToolTip="Close viewer" IconData="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z " HorizontalAlignment="Right" Margin="7,0" Click="CloseViewerButton_Click"></mui:ModernButton>
</StackPanel>
</Grid>
<ScrollViewer Background="{StaticResource ScrollBarBackground}">
<StackPanel x:Name="PdfViewerInnerStackPanel" Background="{StaticResource ScrollBarBackground}">
<!--<Image Width="25" Height="25" x:Name="MyImage"></Image>
<Image Width="25" Height="25"></Image>-->
</StackPanel>
</ScrollViewer>
</StackPanel>
</Grid>
</Grid>
I ended up using a grid as suggested earlier. This is certainly a manual approach involving 2 main methods in the code-behind. Hope this helps someone else.
The first calculates the appropriate row and column position for each tile:
private void ReDrawAttachmentsGrid()
{
foreach (var child in AttachmentsPanel.Children)
{
if (child is PdfAttachment)
{
var childAsPdfAttachment = child as PdfAttachment;
var indexOfAttachment = PdfAttachments.IndexOf(childAsPdfAttachment);
var tilesPerRow = (int)Math.Floor(AttachmentsOuterGrid.ActualWidth / 112);
var desiredRowIndex = (int)(indexOfAttachment / tilesPerRow);
desiredRowIndex += desiredRowIndex;
var desiredColumnIndex = (int)(indexOfAttachment % tilesPerRow);
if (AttachmentsPanel.RowDefinitions.Count - 1 < desiredRowIndex)
{
while (AttachmentsPanel.RowDefinitions.Count - 1 < desiredRowIndex)
AttachmentsPanel.RowDefinitions.Add(new RowDefinition());
}
if (AttachmentsPanel.ColumnDefinitions.Count - 1 < desiredColumnIndex)
{
var column = new ColumnDefinition();
column.Width = new GridLength(112);
AttachmentsPanel.ColumnDefinitions.Add(column);
}
Grid.SetColumn(childAsPdfAttachment, desiredColumnIndex);
Grid.SetRow(childAsPdfAttachment, desiredRowIndex);
}
}
The second shows a viewer on the row below the one being clicked, like so:
var pdfViewer = new PdfViewer(e.Images, e.ByteArray, e.DisplayName, e.AttachmentKey);
pdfViewer.AttachmentClosed += pdfViewer_AttachmentClosed;
AttachmentsPanel.Children.Add(pdfViewer);
Grid.SetColumnSpan(pdfViewer, 100);
var desiredRowIndex = Grid.GetRow(senderAsPdfAttachment) + 1;
if (AttachmentsPanel.RowDefinitions.Count - 1 < desiredRowIndex)
{
while (AttachmentsPanel.RowDefinitions.Count - 1 < desiredRowIndex)
AttachmentsPanel.RowDefinitions.Add(new RowDefinition());
}
Grid.SetRow(pdfViewer, desiredRowIndex);

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