Improve the universal windows phone performance? - c#

I have 10 rectangles inside the canvas, in the ManipulationDelta event, I have to change the height and width. It is working properly in the windows desktop but it takes some time in universal Windows device(phone) when manipulating the rectangle.How to smoothly manipulating the UI element in the windows device. Please suggest me, is any other way to solve this problem?
Here is my code:
<Canvas Name="LayoutRoot" Width="300" Height="500">
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Red" Height="100" Width="100"/>
<Rectangle Fill="Green" Height="100" Width="100"/>
</Canvas>
private void MainPage_OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
foreach (Rectangle rectAngle in LayoutRoot.Children)
{
rectAngle.Width += e.Cumulative.Scale;
rectAngle.Height += e.Cumulative.Scale;
Canvas.SetLeft(rectAngle, LayoutRoot.Width / 2 - rectAngle.ActualWidth / 2);
Canvas.SetTop(rectAngle, LayoutRoot.Height / 2 - rectAngle.ActualHeight / 2);
}
}

You have to use RenderTransform like a TranslateTransform to move your elements, because they are not dependant properties and are performance oriented. So instead using the Canvas Top and Left properties set RenderTransformOrigin and a TranslateTransform.
You will notice a really increased performance.
private void MainPage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
foreach (Rectangle child in LayoutRoot.Children)
{
child.Width += e.Cumulative.Scale;
child.Height += e.Cumulative.Scale;
//This is C#7 in case C#6 adapt
if(child.RenderTransform is TranslateTransform tf)
{
tf.X = LayoutRoot.Width / 2 - child.ActualWidth / 2;
tf.Y = LayoutRoot.Height / 2 - child.ActualHeight / 2;
}
}
}
private void Initialize()
{
var redbrush = new SolidColorBrush(Colors.Red);
foreach (Rectangle child in LayoutRoot.Children)
{
child.Fill = redbrush;
child.Height = 100;
child.Width = 100;
child.RenderTransformOrigin = new Point(0.5, 0.5);
child.RenderTransform = new TranslateTransform();
}
}

Related

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;
}
}

Mouse Handling on Image Control of WPF

I am quite noob in WPF. I am creating a WPF application and using a EmguCV library for image Processing. I found that I can't use ImageBox in WPF. So I am using NamedWindow to show image then I decided to use Image Controlto show the image on the window. I am trying to draw the rectangle over that image but rectangle in not drawn at other place. So can anyone tell me what is wrong in the code.
Basically I want to take ROI of that image.
EDIT:-
I put the Canvas inside grid a put the Image Control inside that Canvas
**My XAML Code **
<Grid Margin="0,0,2,-1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="138*"/>
<ColumnDefinition Width="139*"/>
</Grid.ColumnDefinitions>
<Button x:Name="button" Content="Convert" Margin="139.053,432.066,0,0" Click="button_Click" Height="27.934" VerticalAlignment="Top" HorizontalAlignment="Left" Width="113.947" Grid.Column="1"/>
<Button x:Name="button1" Content="Load Palette" Margin="308,432.066,0,0" Click="button1_Click_1" Height="27.934" VerticalAlignment="Top" HorizontalAlignment="Left" Width="75" Grid.ColumnSpan="2"/>
<Button x:Name="button2" Content="Load Gray Image" Margin="48,432.066,0,0" Click="button2_Click" Height="27.934" VerticalAlignment="Top" HorizontalAlignment="Left" Width="104"/>
<Canvas x:Name="MyCanvas" Margin="81,86.5,27.245,120.5" Grid.Column="1">
<Image x:Name="image3" Height="263" Width="238"/>
</Canvas>
<Image x:Name="image1" HorizontalAlignment="Left" Height="263" Margin="10,86.5,0,0" VerticalAlignment="Top" Width="255.5"/>
<Image x:Name="image2" Grid.ColumnSpan="2" HorizontalAlignment="Left" Height="393" Margin="308,10,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
And My C# code is
private Boolean isdragging = false;
private System.Windows.Point startPoint;
private System.Windows.Point endPoint;
private System.Windows.Shapes.Rectangle rect;
private void image3_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(MyCanvas);
isdragging = true;
if(rect != null)
MyCanvas.Children.Remove(rect);
rect = new System.Windows.Shapes.Rectangle
{
Stroke = System.Windows.Media.Brushes.LightBlue,
StrokeThickness = 2
};
System.Windows.Controls.Canvas.SetLeft(rect, startPoint.X);
System.Windows.Controls.Canvas.SetTop(rect, startPoint.Y);
MyCanvas.Children.Add(rect);
}
private void image3_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
rect = null;
isdragging = false;
endPoint = e.GetPosition(MyCanvas);
}
private void image3_MouseMove(object sender, MouseEventArgs e)
{
if (isdragging == true)
{
var pos = e.GetPosition(MyCanvas);
var x = Math.Min(pos.X, startPoint.X);
var y = Math.Min(pos.Y, startPoint.Y);
var w = Math.Max(pos.X, startPoint.X) - x;
var h = Math.Max(pos.Y, startPoint.Y) - y;
rect.Width = w;
rect.Height = h;
System.Windows.Controls.Canvas.SetLeft(rect, x);
System.Windows.Controls.Canvas.SetTop(rect, y);
}
}
I am using Event Handler over the Canvas but it doesn't showing the rectangle
Thanks in Advance
Thanks Clemens, I have got the answer, Actually I haven't add the event handler to image3 Image Control that's why it is not showing the output.
<Canvas x:Name="MyCanvas" Margin="81,86.5,27.245,120.5" Grid.Column="1">
<Image x:Name="image3" Height="263" Width="238" MouseLeftButtonDown="image3_MouseLeftButtonDown" MouseLeftButtonUp="image3_MouseLeftButtonUp" MouseMove="image3_MouseMove"/>
</Canvas>

Polygon automatcally adjust to its above polygon

I'm making a Windows Desktop Application that have drag and drop functionality.
I'm using Polygon (And Images later) Shapes for drag and drop. The drag and drop functionality works fine but I want that if user drag any shape from the panel and when he drag other shape then the second shape automatically fix with first shape.
You'll understand it by take a look at below screenshots.
It is the Screen Shot of what happens when I drag Shapes
When user drop the polygon near the other polygon it will automatically adjust itself, if the same polygon drop in other area of canvas than a error will show to the user.
Here is my XAML Code
<Window x:Class="Images.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:Images"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel
VerticalAlignment="Stretch"
Height="Auto">
<DockPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
MinWidth="400"
Margin="10">
<GroupBox
DockPanel.Dock="Left"
Width="350"
Background="Aqua"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
Height="Auto">
<WrapPanel Margin="0,10,0,0" VerticalAlignment="Top">
<Button Name="control" Content="Control" Height="30" Background="BlueViolet" Margin="5" Width="100"/>
<Button Name="motion" Content="Motion" Width="100" Margin="5" Background="Green" Height="30"/>
<Button Name="variable" Content="Variable" Width="100" Margin="5" Background="SeaGreen" Height="30"/>
<Button Name="sensor" Content="Sensor" Width="100" Margin="5" Background="OrangeRed" Height="30"/>
<Button Name="lcd" Content="LCD" Width="100" Margin="5" Height="30" Background="PaleVioletRed"/>
<Button Name="function" Content="Function" Width="100" Margin="5" Height="30" Background="Salmon"/>
<StackPanel Name="heaading" Width="350">
<TextBlock Name="controlName" TextAlignment="Center" Text="Controls"/>
</StackPanel>
<StackPanel Name="userControls" Orientation="Vertical">
<!-- Users Controls Items Goes Here -->
<Polygon Name="startProgram" Points="80,10, 80, 80, 135,80, 135, 45, 205, 45, 205, 80, 260, 80, 260,10" Fill="Chocolate" Stroke="Black" StrokeThickness="2" MouseLeftButtonDown="shape_MouseLeftButtonDown" />
<Polygon Name="endProgram" Fill="BlueViolet" Points="80,40, 80,80, 260,80, 260,40, 200,40, 200,10, 140,10,140,40" Stroke="Black" StrokeThickness="2" MouseLeftButtonDown="shape_MouseLeftButtonDown" />
</StackPanel>
</WrapPanel>
</GroupBox>
<!-- Change this to Canvas for work later -->
<Canvas x:Name="dropArea" DockPanel.Dock="Right" Margin="10" Background="#FF9760BF" Width="Auto" HorizontalAlignment="Stretch" AllowDrop="True" Drop="dropArea_Drop">
</Canvas>
</DockPanel>
</DockPanel>
</Window>
Here is my CS code
namespace Images
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void dropArea_Drop(object sender, DragEventArgs e)
{
var shape = e.Data.GetData(typeof(Polygon)) as Polygon;
Console.WriteLine("Polygon Name : " + shape.Name);
Polygon myPolygon = new Polygon();
myPolygon.Stroke = shape.Stroke;
myPolygon.Fill = shape.Fill;
myPolygon.StrokeThickness = 2;
Canvas.SetTop(myPolygon, e.GetPosition(dropArea).Y);
myPolygon.Points = shape.Points;
dropArea.Children.Add(myPolygon);
myPolygon.MouseRightButtonDown += new MouseButtonEventHandler(dragged_ShapeMouseDown);
}
private void dragged_ShapeMouseDown(object sender, MouseButtonEventArgs e)
{
//Show Options to Delete or set Value to current Polygon
}
private void shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Polygon shape = e.Source as Polygon;
DragDrop.DoDragDrop(shape, shape, DragDropEffects.Copy);
}
}
}
Problem
I'm using Canvas.setTop because without setting it my polygon show over the first.
Here I'm setting the polygon that fix with its above polygon but it can be left or right also as shown in below screenshot.
SOLUTION
For Deleting shape
myPolygon.MouseLeftButtonUp += new MouseButtonEventHandler(dragged_ShapeMouseDown);
private void dragged_ShapeMouseDown(object sender, MouseButtonEventArgs e)
{
if (dropArea.Children.Count > 0)
dropArea.Children.Remove(sender as Polygon);
}
Sacha Barber has got very nice article that describe exactly what you are trying to do i think...4 steps articles up to MVVM ! have a look these step1 and
step2, step3, step4 - I also used it in my own project ArchX
well, i think everything is there in my code : during onmove test the result and change the cursor. ondragend : use a HitHelper to determine where you release the mouse and return the shape his tested - then adjust the shape of the polygon regarding the hit result : below sample code - GuideLineManager
public Cursor HitTestGuide(Point p, RulerOrientation mode)
{
if (_Guides.Exists(g => (int)g.Info.Orientation == (int)mode && g.HitTest(p)))
{
return _Guides.First(g => (int)g.Info.Orientation == (int)mode && g.HitTest(p)).Cursor;
}
return Cursors.Arrow;
}
and the onDragEnd, call to get the hit tested object
public Guideline GetSnapGuide(Point hitPoint)
{
foreach (Guideline gl in Guides)
{
if (!gl.IsDisplayed) continue;
if (gl.Info.IsSnap && !gl.Info.IsMoving)
if (gl.IsOnGuide(hitPoint, _Container.dPicCapture))
{
return gl;
}
}
return null;
}

Fill Ellipse based on percentage

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).

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