Related
We have 3D models in WPF and use a cursor made from a Canvas and some internal parts. We turn the HW cursor off and move the Canvas through MouseEvent. The issue is that there are terrible artifacts on the screen as you move from left to right (not nearly as bad right to left). I have played with snaptodevicepixels, Edge mode and nvidea AA settings but the only thing that "fixes" is setting edge mode to aliased for the 3dviewport - and we don't want that. I have even made the cursor completely transparent and it still leaves artifacts.
I broke out some of the code for demonstration. You can especially see the artifacts moving upper-left to lower-right.
Anyone think they can help me out here? I'm head banging and not in a good way. It's looking more like a bug in the AA code.
Thanks
T
FYI: I used some Petzold code here for the beach ball.
Bad AA BeachBall
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace Cursoraa2
{
class Program : Window
{
[STAThread]
public static void Main() => new Application().Run(new Program());
public Program()
{
Width = 500;
Height = 500;
var grid = new Grid();
var viewport = new Viewport3D();
grid.Children.Add(viewport);
Content = grid;
//RenderOptions.SetEdgeMode(viewport,EdgeMode.Aliased);
DynamicCurosr.Start(grid, grid, 40, Color.FromArgb(40, 0x33, 0x33, 0xff), Colors.Blue);
MouseMove += MainWindow_MouseMove;
MakeBeachBallSphere(viewport);
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e) => DynamicCurosr.Move(e.GetPosition(this));
public void MakeBeachBallSphere(Viewport3D viewport)
{
// Get the MeshGeometry3D from the GenerateSphere method.
var mesh = GenerateSphere(new Point3D(0, 0, 0), 1, 36, 18);
mesh.Freeze();
// Define a brush for the sphere.
var brushes = new Brush[6] { Brushes.Red, Brushes.Blue,
Brushes.Yellow, Brushes.Orange,
Brushes.White, Brushes.Green };
var drawgrp = new DrawingGroup();
for (var i = 0; i < brushes.Length; i++)
{
var rectgeo = new RectangleGeometry(new Rect(10 * i, 0, 10, 60));
var geodraw = new GeometryDrawing(brushes[i], null, rectgeo);
drawgrp.Children.Add(geodraw);
}
var drawbrsh = new DrawingBrush(drawgrp);
drawbrsh.Freeze();
// Define the GeometryModel3D.
var geomod = new GeometryModel3D
{
Geometry = mesh,
Material = new DiffuseMaterial(drawbrsh)
};
// Create a ModelVisual3D for the GeometryModel3D.
var modvis = new ModelVisual3D { Content = geomod };
viewport.Children.Add(modvis);
// Create another ModelVisual3D for light.
var modgrp = new Model3DGroup();
modgrp.Children.Add(new AmbientLight(Color.FromRgb(128, 128, 128)));
modgrp.Children.Add(new DirectionalLight(Color.FromRgb(128, 128, 128), new Vector3D(2, -3, -1)));
modvis = new ModelVisual3D {Content = modgrp};
viewport.Children.Add(modvis);
// Create the camera.
var cam = new PerspectiveCamera(new Point3D(0, 0, 8), new Vector3D(0, 0, -1), new Vector3D(0, 1, 0), 45);
viewport.Camera = cam;
// Create a transform for the GeometryModel3D.
var axisangle = new AxisAngleRotation3D(new Vector3D(1, 1, 0), 180);
var rotate = new RotateTransform3D(axisangle);
geomod.Transform = rotate;
// Animate the RotateTransform3D.
//DoubleAnimation anima = new DoubleAnimation(360, new Duration(TimeSpan.FromSeconds(5)));
//anima.RepeatBehavior = RepeatBehavior.Forever;
//axisangle.BeginAnimation(AxisAngleRotation3D.AngleProperty, anima);
}
MeshGeometry3D GenerateSphere(Point3D center, double radius, int slices, int stacks)
{
// Create the MeshGeometry3D.
var mesh = new MeshGeometry3D();
// Fill the Position, Normals, and TextureCoordinates collections.
for (var stack = 0; stack <= stacks; stack++)
{
var phi = Math.PI / 2 - stack * Math.PI / stacks;
var y = radius * Math.Sin(phi);
var scale = -radius * Math.Cos(phi);
for (var slice = 0; slice <= slices; slice++)
{
var theta = slice * 2 * Math.PI / slices;
var x = scale * Math.Sin(theta);
var z = scale * Math.Cos(theta);
var normal = new Vector3D(x, y, z);
mesh.Normals.Add(normal);
mesh.Positions.Add(normal + center);
mesh.TextureCoordinates.Add(
new Point((double)slice / slices,
(double)stack / stacks));
}
}
// Fill the TriangleIndices collection.
for (var stack = 0; stack < stacks; stack++)
for (var slice = 0; slice < slices; slice++)
{
var n = slices + 1; // Keep the line length down.
if (stack != 0)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
}
if (stack != stacks - 1)
{
mesh.TriangleIndices.Add((stack + 0) * n + slice + 1);
mesh.TriangleIndices.Add((stack + 1) * n + slice);
mesh.TriangleIndices.Add((stack + 1) * n + slice + 1);
}
}
return mesh;
}
}
public static class DynamicCurosr
{
static public bool InSession { get; private set; }
private static Panel theCursor;
private static readonly ScaleTransform ScaleTransform = new ScaleTransform(1, 1);
private static readonly MatrixTransform MatrixTransform = new MatrixTransform(1, 0, 0, 1, 0, 0);
private static Color defaultFill = Color.FromArgb(20, 255, 255, 255);
private static Color fillFromUser;
private static double strokeFromUser = 0;
private static Color strokeColorFromUser = Colors.Black;
private static int centerDotSizeFromUser = 10; // need to get from user
private static double initialDiameter = double.NaN;
private static Panel cursorPanel;
private static Panel mousePanel;
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius)
{
return Start(cursorPanelIn, mousePanelIn, radius, defaultFill);
}
public static bool Start(Panel cursorPanelIn, Panel mousePanelIn, double radius, Color fill, Color strokeColor = default(Color), double strokeSize = .16)
{
strokeColor = strokeColor == default(Color) ? Colors.Black : strokeColor;
strokeColorFromUser = strokeColor;
fillFromUser = fill;
strokeFromUser = strokeColor == default(Color) ? 0 : strokeSize;
initialDiameter = double.IsNaN(initialDiameter) ? radius * 2 : initialDiameter;
return Start(cursorPanelIn, mousePanelIn);
}
private static bool Start(Panel cursorPanelIn, Panel mousePanelIn)
{
if (InSession) return false;
cursorPanel = cursorPanelIn;
mousePanel = mousePanelIn;
var point = Mouse.GetPosition(cursorPanel);
theCursor = MakeACursor(theCursor, initialDiameter / 2);
InSession = true;
cursorPanel.Cursor = Cursors.None;
theCursor.Visibility = Visibility.Visible;
Move(point);
if (cursorPanel.Children.Contains(theCursor))
return false;
cursorPanel.Children.Add(theCursor);
Mouse.OverrideCursor = Cursors.None;
return true;
}
public static void Stop()
{
if (InSession)
{
Mouse.OverrideCursor = null;
theCursor.Visibility = Visibility.Collapsed;
cursorPanel.Children.Remove(theCursor);
InSession = false;
}
}
public static void Move(Point point)
{
if (InSession && theCursor.Visibility == Visibility.Visible)
{
var m = MatrixTransform.Matrix;
m.OffsetX = point.X - theCursor.Width / 2;
m.OffsetY = point.Y - theCursor.Height / 2;
MatrixTransform.Matrix = m;
theCursor.RenderTransform = MatrixTransform;
}
}
public static Panel MakeACursor(Panel theCursor, double radius, Color fillColorIn = default(Color), Color strokeColorIn = default(Color))
{
var strokeColor = new SolidColorBrush(strokeColorIn == default(Color) ? strokeColorFromUser : strokeColorIn);
if (theCursor == null)
{
theCursor = new Grid()
{
Width = radius * 2,
Height = radius * 2,
Background = null,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
RenderTransform = ScaleTransform,
RenderTransformOrigin = new Point(.5, .5),
};
var cursorElement = new Ellipse
{
Width = radius * 2,
Height = radius * 2,
Fill = new SolidColorBrush(fillColorIn == default(Color) ? fillFromUser : fillColorIn),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(cursorElement);
}
MakeCursorOverlay(theCursor, radius, strokeColor);
return theCursor;
}
public static void MakeCursorOverlay(Panel theCursor, double radius, SolidColorBrush strokeColor)
{
var save = theCursor.Children[0];
theCursor.Children.Clear();
theCursor.Children.Add(save);
var circle = new Ellipse
{
Width = centerDotSizeFromUser,
Height = centerDotSizeFromUser,
Fill = null,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
StrokeThickness = strokeFromUser,
Stroke = strokeColor,
RenderTransformOrigin = new Point(.5, .5)
};
theCursor.Children.Add(circle);
}
}
}
I want a textbox where you can input text from keyboard but be shown as an arc. Is it possible ?
I have found this solution in codeproject. Author created TextOnAPath control which can display curved text.
Source code:
[ContentProperty("Text")]
public class TextOnAPath : Control
{
static TextOnAPath()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextOnAPath), new FrameworkPropertyMetadata(typeof(TextOnAPath)));
Control.FontSizeProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontFamilyProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStretchProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStyleProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontWeightProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
}
static void OnFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == null || e.NewValue == e.OldValue)
return;
textOnAPath.UpdateText();
textOnAPath.Update();
}
double[] _segmentLengths;
TextBlock[] _textBlocks;
Panel _layoutPanel;
bool _layoutHasValidSize = false;
#region Text DP
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TextOnAPath),
new PropertyMetadata(null, new PropertyChangedCallback(OnStringPropertyChanged),
new CoerceValueCallback(CoerceTextValue)));
static void OnStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
{
if (textOnAPath._layoutPanel != null)
textOnAPath._layoutPanel.Children.Clear();
return;
}
textOnAPath.UpdateText();
textOnAPath.Update();
}
static object CoerceTextValue(DependencyObject d, object baseValue)
{
if ((String)baseValue == "")
return null;
return baseValue;
}
#endregion
#region TextPath DP
public Geometry TextPath
{
get { return (Geometry)GetValue(TextPathProperty); }
set { SetValue(TextPathProperty, value); }
}
public static readonly DependencyProperty TextPathProperty =
DependencyProperty.Register("TextPath", typeof(Geometry), typeof(TextOnAPath),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnTextPathPropertyChanged)));
static void OnTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
#region DrawPath DP
/// <summary>
/// Set this property to True to display the TextPath geometry in the control
/// </summary>
public bool DrawPath
{
get { return (bool)GetValue(DrawPathProperty); }
set { SetValue(DrawPathProperty, value); }
}
public static readonly DependencyProperty DrawPathProperty =
DependencyProperty.Register("DrawPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawPathPropertyChanged)));
static void OnDrawPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region DrawLinePath DP
/// <summary>
/// Set this property to True to display the line segments under the text (flattened path)
/// </summary>
public bool DrawLinePath
{
get { return (bool)GetValue(DrawLinePathProperty); }
set { SetValue(DrawLinePathProperty, value); }
}
// Using a DependencyProperty as the backing store for DrawFlattendPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DrawLinePathProperty =
DependencyProperty.Register("DrawLinePath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawLinePathPropertyChanged)));
static void OnDrawLinePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region ScaleTextPath DP
/// <summary>
/// If set to True (default) then the geometry defined by TextPath automatically gets scaled to fit the width/height of the control
/// </summary>
public bool ScaleTextPath
{
get { return (bool)GetValue(ScaleTextPathProperty); }
set { SetValue(ScaleTextPathProperty, value); }
}
public static readonly DependencyProperty ScaleTextPathProperty =
DependencyProperty.Register("ScaleTextPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnScaleTextPathPropertyChanged)));
static void OnScaleTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue)
return;
bool value = (Boolean)e.NewValue;
if (value == false && textOnAPath.TextPath != null)
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
void UpdateText()
{
if (Text == null || FontFamily == null || FontWeight == null || FontStyle == null)
return;
_textBlocks = new TextBlock[Text.Length];
_segmentLengths = new double[Text.Length];
for (int i = 0; i < Text.Length; i++)
{
TextBlock t = new TextBlock();
t.FontSize = this.FontSize;
t.FontFamily = this.FontFamily;
t.FontStretch = this.FontStretch;
t.FontWeight = this.FontWeight;
t.FontStyle = this.FontStyle;
t.Text = new String(Text[i], 1);
t.RenderTransformOrigin = new Point(0.0, 1.0);
t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_textBlocks[i] = t;
_segmentLengths[i] = t.DesiredSize.Width;
}
}
void Update()
{
if (Text == null || TextPath == null || _layoutPanel == null || !_layoutHasValidSize)
return;
List<Point> intersectionPoints;
intersectionPoints = GeometryHelper.GetIntersectionPoints(TextPath.GetFlattenedPathGeometry(), _segmentLengths);
_layoutPanel.Children.Clear();
_layoutPanel.Margin = new Thickness(FontSize);
for (int i = 0; i < intersectionPoints.Count - 1; i++)
{
double oppositeLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X + _segmentLengths[i] - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0)) / 2.0;
double hypLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0));
double ratio = oppositeLen / hypLen;
if (ratio > 1.0)
ratio = 1.0;
else if (ratio < -1.0)
ratio = -1.0;
//double angle = 0.0;
double angle = 2.0 * Math.Asin(ratio) * 180.0 / Math.PI;
// adjust sign on angle
if ((intersectionPoints[i].X + _segmentLengths[i]) > intersectionPoints[i].X)
{
if (intersectionPoints[i + 1].Y < intersectionPoints[i].Y)
angle = -angle;
}
else
{
if (intersectionPoints[i + 1].Y > intersectionPoints[i].Y)
angle = -angle;
}
TextBlock currTextBlock = _textBlocks[i];
RotateTransform rotate = new RotateTransform(angle);
TranslateTransform translate = new TranslateTransform(intersectionPoints[i].X, intersectionPoints[i].Y - currTextBlock.DesiredSize.Height);
TransformGroup transformGrp = new TransformGroup();
transformGrp.Children.Add(rotate);
transformGrp.Children.Add(translate);
currTextBlock.RenderTransform = transformGrp;
_layoutPanel.Children.Add(currTextBlock);
if (DrawLinePath == true)
{
Line line = new Line();
line.X1 = intersectionPoints[i].X;
line.Y1 = intersectionPoints[i].Y;
line.X2 = intersectionPoints[i + 1].X;
line.Y2 = intersectionPoints[i + 1].Y;
line.Stroke = Brushes.Black;
_layoutPanel.Children.Add(line);
}
}
// don't draw path if already drawing line path
if (DrawPath == true && DrawLinePath == false)
{
Path path = new Path();
path.Data = TextPath;
path.Stroke = Brushes.Black;
_layoutPanel.Children.Add(path);
}
}
public TextOnAPath()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_layoutPanel = GetTemplateChild("LayoutPanel") as Panel;
if (_layoutPanel == null)
throw new Exception("Could not find template part: LayoutPanel");
_layoutPanel.SizeChanged += new SizeChangedEventHandler(_layoutPanel_SizeChanged);
}
Size _newSize;
void _layoutPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
_newSize = e.NewSize;
UpdateSize();
Update();
}
void UpdateSize()
{
if (_newSize == null || TextPath == null)
return;
_layoutHasValidSize = true;
double xScale = _newSize.Width / TextPath.Bounds.Width;
double yScale = _newSize.Height / TextPath.Bounds.Height;
if (TextPath.Bounds.Width <= 0)
xScale = 1.0;
if (TextPath.Bounds.Height <= 0)
xScale = 1.0;
if (xScale <= 0 || yScale <= 0)
return;
if (TextPath.Transform is TransformGroup)
{
TransformGroup grp = TextPath.Transform as TransformGroup;
if (grp.Children[0] is ScaleTransform && grp.Children[1] is TranslateTransform)
{
if (ScaleTextPath)
{
ScaleTransform scale = grp.Children[0] as ScaleTransform;
scale.ScaleX *= xScale;
scale.ScaleY *= yScale;
}
TranslateTransform translate = grp.Children[1] as TranslateTransform;
translate.X += -TextPath.Bounds.X;
translate.Y += -TextPath.Bounds.Y;
}
}
else
{
ScaleTransform scale;
TranslateTransform translate;
if (ScaleTextPath)
{
scale = new ScaleTransform(xScale, yScale);
translate = new TranslateTransform(-TextPath.Bounds.X * xScale, -TextPath.Bounds.Y * yScale);
}
else
{
scale = new ScaleTransform(1.0, 1.0);
translate = new TranslateTransform(-TextPath.Bounds.X, -TextPath.Bounds.Y );
}
TransformGroup grp = new TransformGroup();
grp.Children.Add(scale);
grp.Children.Add(translate);
TextPath.Transform = grp;
}
}
}
public static class GeometryHelper
{
public static List<Point> GetIntersectionPoints(PathGeometry FlattenedPath, double[] SegmentLengths)
{
List<Point> intersectionPoints = new List<Point>();
List<Point> pointsOnFlattenedPath = GetPointsOnFlattenedPath(FlattenedPath);
if (pointsOnFlattenedPath == null || pointsOnFlattenedPath.Count < 2)
return intersectionPoints;
Point currPoint = pointsOnFlattenedPath[0];
intersectionPoints.Add(currPoint);
// find point on flattened path that is segment length away from current point
int flattedPathIndex = 0;
int segmentIndex = 1;
while (flattedPathIndex < pointsOnFlattenedPath.Count - 1 &&
segmentIndex < SegmentLengths.Length + 1)
{
Point? intersectionPoint = GetIntersectionOfSegmentAndCircle(
pointsOnFlattenedPath[flattedPathIndex],
pointsOnFlattenedPath[flattedPathIndex + 1], currPoint, SegmentLengths[segmentIndex - 1]);
if (intersectionPoint == null)
flattedPathIndex++;
else
{
intersectionPoints.Add((Point)intersectionPoint);
currPoint = (Point)intersectionPoint;
pointsOnFlattenedPath[flattedPathIndex] = currPoint;
segmentIndex++;
}
}
return intersectionPoints;
}
static List<Point> GetPointsOnFlattenedPath(PathGeometry FlattenedPath)
{
List<Point> flattenedPathPoints = new List<Point>();
// for flattened geometry there should be just one PathFigure in the Figures
if (FlattenedPath.Figures.Count != 1)
return null;
PathFigure pathFigure = FlattenedPath.Figures[0];
flattenedPathPoints.Add(pathFigure.StartPoint);
// SegmentsCollection should contain PolyLineSegment and LineSegment
foreach (PathSegment pathSegment in pathFigure.Segments)
{
if (pathSegment is PolyLineSegment)
{
PolyLineSegment seg = pathSegment as PolyLineSegment;
foreach (Point point in seg.Points)
flattenedPathPoints.Add(point);
}
else if (pathSegment is LineSegment)
{
LineSegment seg = pathSegment as LineSegment;
flattenedPathPoints.Add(seg.Point);
}
else
throw new Exception("GetIntersectionPoint - unexpected path segment type: " + pathSegment.ToString());
}
return (flattenedPathPoints);
}
static Point? GetIntersectionOfSegmentAndCircle(Point SegmentPoint1, Point SegmentPoint2,
Point CircleCenter, double CircleRadius)
{
// linear equation for segment: y = mx + b
double slope = (SegmentPoint2.Y - SegmentPoint1.Y) / (SegmentPoint2.X - SegmentPoint1.X);
double intercept = SegmentPoint1.Y - (slope * SegmentPoint1.X);
// special case when segment is vertically oriented
if (double.IsInfinity(slope))
{
double root = Math.Pow(CircleRadius, 2.0) - Math.Pow(SegmentPoint1.X - CircleCenter.X, 2.0);
if (root < 0)
return null;
// soln 1
double SolnX1 = SegmentPoint1.X;
double SolnY1 = CircleCenter.Y - Math.Sqrt(root);
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = SegmentPoint1.X;
double SolnY2 = CircleCenter.Y + Math.Sqrt(root);
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
else
{
// use soln to quadradratic equation to solve intersection of segment and circle:
// x = (-b +/ sqrt(b^2-4ac))/(2a)
double a = 1 + Math.Pow(slope, 2.0);
double b = (-2 * CircleCenter.X) + (2 * (intercept - CircleCenter.Y) * slope);
double c = Math.Pow(CircleCenter.X, 2.0) + Math.Pow(intercept - CircleCenter.Y, 2.0) - Math.Pow(CircleRadius, 2.0);
// check for no solutions, is sqrt negative?
double root = Math.Pow(b, 2.0) - (4 * a * c);
if (root < 0)
return null;
// we might have two solns...
// soln 1
double SolnX1 = (-b + Math.Sqrt(root)) / (2 * a);
double SolnY1 = slope * SolnX1 + intercept;
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = (-b - Math.Sqrt(root)) / (2 * a);
double SolnY2 = slope * SolnX2 + intercept;
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
// shouldn't get here...but in case
return null;
}
static bool IsBetween(double X, double X1, double X2)
{
if (X1 >= X2 && X <= X1 && X >= X2)
return true;
if (X1 <= X2 && X >= X1 && X <= X2)
return true;
return false;
}
}
Usage:
<TextOnAPath:TextOnAPath FontSize="30" DrawPath="True"
Text="The quick brown fox jumped over the lazy hen.">
<TextOnAPath:TextOnAPath.TextPath>
<PathGeometry Figures="M0,0 C120,361 230.5,276.5 230.5,276.5
L308.5,237.50001 C308.5,237.50001 419.5,179.5002 367.5,265.49993
315.5,351.49966 238.50028,399.49924 238.50028,399.49924 L61.500017,
420.49911"/>
</TextOnAPath:TextOnAPath.TextPath>
</TextOnAPath:TextOnAPath>
</Grid>
I am doing a program in C# for kids with which kids can test their knowledge of Multiplication tables.
I cannot get the value of 'i' in the Result_Leave() function to track which value of the text box array is incorrect.
For Example:
5 X 1 = [ ] <---- an array of textboxes with name Result[i]"
the user enters the value of 5*1 in the text box and my program instantly checks if the entered value is correct or not, using the "Leave" event for the text box.
I have used an array of text boxes...
So I cannot track which Result[i] contains the incorrect value...
I have fired the function "Result_Leave" for each of the text boxes "Result[i]"
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Tables
{
public partial class FormMain : Form
{
private System.Windows.Forms.Label[] labelNumber;
private System.Windows.Forms.Label[] labelCross;
private System.Windows.Forms.Label[] labelTableOf;
private System.Windows.Forms.Label[] labelEquals;
/*"Result" is an array of textboxes which takes the result of the multiplication from the user*/
private System.Windows.Forms.TextBox[] Result; //declaration
public FormMain()
{
InitializeComponent();
WindowState = FormWindowState.Maximized;
buttonCheckAnswers.Enabled = false;
}
private void buttonGo_Click(object sender, EventArgs e)
{
if (textBoxInput.Text == "")
{
errorProvider1.SetError(textBoxInput, "Hey! Enter a number please");
}
else
{
textBoxInput.Enabled = false;
buttonCheckAnswers.Enabled = true;
labelNumber = new System.Windows.Forms.Label[10];
labelCross = new System.Windows.Forms.Label[10];
labelTableOf = new System.Windows.Forms.Label[10];
labelEquals = new System.Windows.Forms.Label[10];
Result = new System.Windows.Forms.TextBox[10];
for (int i = 0; i < 10; i++)
{
// this snippet generates code for adding controls at runtime viz. textboxes and labels
labelNumber[i] = new Label();
this.labelNumber[i].AutoSize = true;
this.labelNumber[i].Location = new System.Drawing.Point(200, 163 + 55 * i);
this.labelNumber[i].Name = "labelNumber";
this.labelNumber[i].Size = new System.Drawing.Size(35, 13);
this.labelNumber[i].Text = (i + 1).ToString();
this.labelNumber[i].Font = new System.Drawing.Font("Comic Sans MS", 17F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelNumber[i].ForeColor = System.Drawing.Color.Khaki;
this.Controls.AddRange(new System.Windows.Forms.Control[] { labelNumber[i] });
labelCross[i] = new Label();
this.labelCross[i].AutoSize = true;
this.labelCross[i].Location = new System.Drawing.Point(150, 163 + 55 * i);
this.labelCross[i].Name = "labelCross";
this.labelCross[i].Size = new System.Drawing.Size(35, 13);
this.labelCross[i].Text = "X";
this.labelCross[i].Font = new System.Drawing.Font("Comic Sans MS", 17F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelCross[i].ForeColor = System.Drawing.Color.Khaki;
this.Controls.AddRange(new System.Windows.Forms.Control[] { labelCross[i] });
labelTableOf[i] = new Label();
this.labelTableOf[i].AutoSize = true;
this.labelTableOf[i].Location = new System.Drawing.Point(100, 163 + 55 * i);
this.labelTableOf[i].Name = "labelTableOf";
this.labelTableOf[i].Size = new System.Drawing.Size(35, 13);
this.labelTableOf[i].Text = textBoxInput.Text;
this.labelTableOf[i].Font = new System.Drawing.Font("Comic Sans MS", 17F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelTableOf[i].ForeColor = System.Drawing.Color.Khaki;
this.Controls.AddRange(new System.Windows.Forms.Control[] { labelTableOf[i] });
labelEquals[i] = new Label();
this.labelEquals[i].AutoSize = true;
this.labelEquals[i].Location = new System.Drawing.Point(250, 163 + 55 * i);
this.labelEquals[i].Name = "labelTableOf";
this.labelEquals[i].Size = new System.Drawing.Size(35, 13);
this.labelEquals[i].Text = "=";
this.labelEquals[i].Font = new System.Drawing.Font("Comic Sans MS", 17F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelEquals[i].ForeColor = System.Drawing.Color.Khaki;
this.Controls.AddRange(new System.Windows.Forms.Control[] { labelEquals[i] });
/*"Result" is an array of textboxes which takes the result of the multiplication from the user*/
Result[i] = new TextBox();
this.Result[i].BackColor = System.Drawing.Color.BlueViolet;
this.Result[i].Font = new System.Drawing.Font("Comic Sans MS", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Result[i].ForeColor = System.Drawing.SystemColors.Info;
this.Result[i].Location = new System.Drawing.Point(300, 163 + 55 * i);
this.Result[i].Name = "Result" + i;
this.Result[i].Size = new System.Drawing.Size(57, 37);
this.Result[i].TabIndex = i;
/*this is where the problem arises...*/
this.Result[i].Leave += new System.EventHandler(this.Result_Leave);// how do I send the value of 'i' to Result_Leave() function
/*Note - Result_Leave() is FIRED when the cursor moves away from the "Result" textbox*/
this.Controls.AddRange(new System.Windows.Forms.Control[] { Result[i] });
}
}
}
private void textBoxInput_TextChanged(object sender, EventArgs e)
{
errorProvider1.Clear();
}
private void radioButtonInstantChecking_CheckedChanged(object sender, EventArgs e)
{
if (radioButtonCheckAtLast.Checked == true && textBoxInput.Text!="")
{
buttonCheckAnswers.Enabled = true;
}
else buttonCheckAnswers.Enabled = false;
}
private void Result_Leave(object sender, EventArgs e)
{
/*Code for checking multiplication goes here*/
/*If multiplication result entered by the user is
*correct change the background colour of the corresponding textbox "Result[i] to GREEN else BLUE"
*as in buttonCheckAnswers_Click() function...
*/
}
private void textBoxInput_KeyPress(object sender, KeyPressEventArgs e)
{
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar) && e.KeyChar != '.')
{
e.Handled = true;
}
// only allow one decimal point
if (e.KeyChar == '.' && (sender as TextBox).Text.IndexOf('.') > -2)
e.Handled = true;
}
private void buttonCheckAnswers_Click(object sender, EventArgs e)
{
int score=0;
bool flag=false;
for (int i = 0; i < 10; i++)
{
if (Result[i].Text == "")
{
flag = true;
break;
}
else if ((Convert.ToInt32(Result[i].Text)) != ((Convert.ToInt32(labelNumber[i].Text) * (Convert.ToInt32(labelTableOf[i].Text)))))
{
Result[i].BackColor = System.Drawing.Color.Red;
}
else
{
Result[i].BackColor = System.Drawing.Color.Green;
score += 1;
}
}
if (score == 10)
labelComments.Text = "Well done kid! Full Marks!\nYou know your table of\n"+textBoxInput.Text+" very well"+"\nScore = "+score;
else if(flag)
labelComments.Text = "Oops! \nComplete your table kid!";
else
labelComments.Text = "Oops! \nThere are errors. \nPlease revise your tables!" + "\nYour score is : " + score;
}
}
}
One quick-and-dirty way to do this is to set a value in each TextBox's Tag property. In the for loop inside buttonGo_Click, you could set Result[i].Tag = i;, then in Result_Leave you could do:
int number = (int)((sender as TextBox).Tag);
I am creating an application just like a paint in WPF, and I want to add zoom functionality to it. I am taking canvas as a parent and writable bitmap on it as child on which I draw. When the size of the canvas is small, I am drawing on writable bitmap smoothly, but when the size of the canvas is large, and zoom it, canvas size will be large, problem occur to draw on this large area. So I want to find the visible region of the canvas so that I can draw on it smoothly.
Please give me a source code to find the visible region of the canvas.
I have create this application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
namespace MapDesigner.Controls
{
class MapCanvas : Canvas
{
#region Routed Events
public static readonly RoutedEvent SelectedColorChangeEvent = EventManager.RegisterRoutedEvent(
"SelectedColorChange", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ucToolBox));
public event RoutedEventHandler SelectedColorChange
{
add { AddHandler(SelectedColorChangeEvent, value); }
remove { RemoveHandler(SelectedColorChangeEvent, value); }
}
#endregion
#region Enums
public enum Tool
{
Pencil,
FloodFill,
Eraser,
RectSelect,
Brush,
Part
}
#endregion
WriteableBitmap _wBMP;
Image _dispImg = new Image();
ScaleTransform st = new ScaleTransform();
int canvasHeight, canvasWidth;
double zoomLevel = 1;
Border brdGrid = new Border();
Color cellColor = Colors.Black;
Tool currentTool = Tool.Pencil;
int[,] array;
bool drawing = false;
bool showGrids = true;
public TextBlock tbPos;
public Tool CurrentTool
{
get
{
return currentTool;
}
set
{
currentTool = value;
}
}
public Color CellColor
{
get
{
return cellColor;
}
set
{
cellColor = value;
}
}
public bool GridsVisible
{
get
{
return showGrids;
}
set
{
showGrids = value;
}
}
public MapCanvas()
{
this.Children.Clear();
this.Children.Add(_dispImg);
//st.ScaleX = 1;
//st.ScaleY = 1;
// this.LayoutTransform = st;
}
void Refresh()
{
//canvas = new MapCanvas();
this.Children.Clear();
this.Children.Add(_dispImg);
st.ScaleX = 1;
st.ScaleY = 1;
this.Height = 0;
this.Width = 0;
zoomLevel = 1;
drawing = false;
}
public void LoadBMP(Uri bmpUri)
{
Refresh();
BitmapImage bmi = new BitmapImage(bmpUri);
_wBMP = new WriteableBitmap(bmi);
_dispImg.Source = _wBMP;
this.Height = bmi.Height;
this.Width = bmi.Width;
ShowGrids();
}
public void CreateBMP(int width, int height)
{
Refresh();
_wBMP = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr32, BitmapPalettes.WebPalette);
_wBMP.setPixel(Colors.White);
_dispImg.Source = _wBMP;
this.Height = height;
this.Width = width;
ShowGrids();
}
public void CreateNewDesign(Size mapSize)
{
Refresh();
_wBMP = new WriteableBitmap((int)mapSize.Width, (int)mapSize.Width, 96, 96, PixelFormats.Bgr32, BitmapPalettes.WebPalette);
_wBMP.setPixel(Colors.White);
_dispImg.Source = _wBMP;
array = new int[(_wBMP.PixelHeight + 1), (_wBMP.PixelWidth + 1)];
canvasWidth = (int)mapSize.Width;
canvasHeight = (int)mapSize.Height;
this.Height = mapSize.Height;
this.Width = mapSize.Width;
ShowGrids();
}
void ShowGrids()
{
return;
double width = 1;// _tileWidth + _tileMargin;
double height = 1;// _tileHeight + _tileMargin;
double numTileToAccumulate = 16;
Polyline gridCell = new Polyline();
gridCell.Margin = new Thickness(.5);
gridCell.Stroke = Brushes.LightBlue;
gridCell.StrokeThickness = 0.1;
gridCell.Points = new PointCollection(new Point[] { new Point(0, height-0.1),
new Point(width-0.1, height-0.1), new Point(width-0.1, 0) });
VisualBrush gridLines = new VisualBrush(gridCell);
gridLines.TileMode = TileMode.Tile;
gridLines.Viewport = new Rect(0, 0, 1.0 / numTileToAccumulate, 1.0 / numTileToAccumulate);
gridLines.AlignmentX = AlignmentX.Center;
gridLines.AlignmentY = AlignmentY.Center;
VisualBrush outerVB = new VisualBrush();
Rectangle outerRect = new Rectangle();
outerRect.Width = 10.0; //can be any size
outerRect.Height = 10.0;
outerRect.Fill = gridLines;
outerVB.Visual = outerRect;
outerVB.Viewport = new Rect(0, 0,
width * numTileToAccumulate, height * numTileToAccumulate);
outerVB.ViewportUnits = BrushMappingMode.Absolute;
outerVB.TileMode = TileMode.Tile;
this.Children.Remove(brdGrid);
brdGrid = new Border();
brdGrid.Height = this.Height;
brdGrid.Width = this.Width;
brdGrid.Background = outerVB;
this.Children.Add(brdGrid);
}
protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
{
base.OnMouseMove(e);
tbPos.Text = (_wBMP.PixelWidth / zoomLevel).ToString() + "," + (_wBMP.PixelHeight / zoomLevel).ToString() + " | " + Math.Ceiling((((Point)e.GetPosition(this)).X) / zoomLevel).ToString() + "," + Math.Ceiling((((Point)e.GetPosition(this)).Y / zoomLevel)).ToString();
if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
{
Point pos = e.GetPosition(this);
int xPos = (int)Math.Ceiling((pos.X) / zoomLevel);
int yPos = (int)Math.Ceiling((pos.Y) / zoomLevel);
int xDraw = (int)Math.Ceiling(pos.X);
int yDraw = (int)Math.Ceiling(pos.Y);
array[xPos, yPos] = 1;
drawing = true;
SetPixelsFromArray((int)zoomLevel);
//for (int i = 0; i < zoomLevel; i++)
//{
// for (int j = 0; j < zoomLevel; j++)
// {
// _wBMP.setPixel(xDraw, yDraw, cellColor);
// _dispImg.Source = _wBMP;
// }
//}
//_wBMP.setPixel(xPos, yPos, cellColor);
//_wBMP.setPixel((int)pos.X, (int)pos.Y, cellColor);
//_dispImg.Source = _wBMP;
}
}
private void SetPixelsFromArray(int ZoomLevel)
{
for (int i = 1; i < _wBMP.PixelWidth / ZoomLevel; i++)
{
for (int j = 1; j < _wBMP.PixelHeight / ZoomLevel; j++)
{
if (array[i, j] == 1)
{
for (int k = 0; k < ZoomLevel; k++)
{
for (int l = 0; l < ZoomLevel; l++)
{
_wBMP.setPixel((int)(i * ZoomLevel + k), (int)(j * ZoomLevel + l), cellColor);
_dispImg.Source = _wBMP;
}
}
}
}
}
}
protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)
{
//double d= this.ActualHeight;
//Double t =(double) this.GetValue(Canvas.TopProperty);
//double i = Convert.ToDouble(top);
getScreenRect();
if (e.ChangedButton == System.Windows.Input.MouseButton.Right)
{
if (cellColor == Colors.Black)
{
cellColor = Colors.Red;
}
else
{
cellColor = Colors.Black;
}
}
else if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
Point pos = e.GetPosition(this);
int xPos = (int)Math.Ceiling((pos.X) / zoomLevel);
int yPos = (int)Math.Ceiling((pos.Y) / zoomLevel);
array[xPos, yPos] = 1;
drawing = true;
SetPixelsFromArray((int)zoomLevel);
//_wBMP.setPixel((int)pos.X, (int)pos.Y, cellColor);
//_dispImg.Source = _wBMP;
}
}
private void getScreenRect()
{
Visual _rootVisual = HwndSource.FromVisual(this).RootVisual;
GeneralTransform transformToRoot = this.TransformToAncestor(_rootVisual);
Rect screenRect = new Rect(transformToRoot.Transform(new Point(0, 0)), transformToRoot.Transform(new Point(this.ActualWidth, this.ActualHeight)));
DependencyObject parent = VisualTreeHelper.GetParent(this);
while (parent != null)
{
Visual visual = parent as Visual;
System.Windows.Controls.Control control = parent as System.Windows.Controls.Control;
if (visual != null && control != null)
{
transformToRoot = visual.TransformToAncestor(_rootVisual);
Point pointAncestorTopLeft = transformToRoot.Transform(new Point(0, 0));
Point pointAncestorBottomRight = transformToRoot.Transform(new Point(control.ActualWidth, control.ActualHeight));
Rect ancestorRect = new Rect(pointAncestorTopLeft, pointAncestorBottomRight);
screenRect.Intersect(ancestorRect);
}
parent = VisualTreeHelper.GetParent(parent);
//}
// at this point screenRect is the bounding rectangle for the visible portion of "this" element
}
// return screenRect;
}
protected override void OnMouseWheel(System.Windows.Input.MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
if (e.Delta > 0)
{
zoomLevel *= 2;
}
else
{
zoomLevel /= 2;
}
if (zoomLevel > 8)
{
zoomLevel = 8;
}
if (zoomLevel <= 1)
{
zoomLevel = 1;
// brdGrid.Visibility = Visibility.Collapsed;
}
else
{
//brdGrid.Visibility = Visibility.Visible;
}
_wBMP = new WriteableBitmap((int)zoomLevel * canvasWidth, (int)zoomLevel * canvasHeight, 96, 96, PixelFormats.Bgr32, BitmapPalettes.WebPalette);
_wBMP.setPixel(Colors.White);
this.Width = zoomLevel * canvasWidth;
this.Height = zoomLevel * canvasHeight;
if (drawing == true)
{
SetPixelsFromArray((int)zoomLevel);
}
//this.InvalidateVisual();
}
internal bool SaveAsBMP(string fileName)
{
return true;
}
}
public static class bitmapextensions
{
public static void setPixel(this WriteableBitmap wbm, Color c)
{
if (!wbm.Format.Equals(PixelFormats.Bgr32))
return;
wbm.Lock();
IntPtr buff = wbm.BackBuffer;
int Stride = wbm.BackBufferStride;
int x = 0;
int y = 0;
for (x = 0; x < wbm.PixelWidth; x++)
{
for (y = 0; y < wbm.PixelHeight; y++)
{
unsafe
{
byte* pbuff = (byte*)buff.ToPointer();
int loc = y * Stride + x * 4;
pbuff[loc] = c.B;
pbuff[loc + 1] = c.G;
pbuff[loc + 2] = c.R;
//pbuff[loc + 3] = c.A;
}
}
}
wbm.AddDirtyRect(new Int32Rect(0, 0, x, y));
wbm.Unlock();
}
public static void setPixel(this WriteableBitmap wbm, int x, int y, Color c)
{
if (y > wbm.PixelHeight - 1 || x > wbm.PixelWidth - 1)
return;
if (y < 0 || x < 0)
return;
if (!wbm.Format.Equals(PixelFormats.Bgr32))
return;
wbm.Lock();
IntPtr buff = wbm.BackBuffer;
int Stride = wbm.BackBufferStride;
unsafe
{
byte* pbuff = (byte*)buff.ToPointer();
int loc = y * Stride + x * 4;
pbuff[loc] = c.B;
pbuff[loc + 1] = c.G;
pbuff[loc + 2] = c.R;
//pbuff[loc + 3] = c.A;
}
wbm.AddDirtyRect(new Int32Rect(x, y, 1, 1));
wbm.Unlock();
}
public static Color getPixel(this WriteableBitmap wbm, int x, int y)
{
if (y > wbm.PixelHeight - 1 || x > wbm.PixelWidth - 1)
return Color.FromArgb(0, 0, 0, 0);
if (y < 0 || x < 0)
return Color.FromArgb(0, 0, 0, 0);
if (!wbm.Format.Equals(PixelFormats.Bgr32))
return Color.FromArgb(0, 0, 0, 0);
IntPtr buff = wbm.BackBuffer;
int Stride = wbm.BackBufferStride;
Color c;
unsafe
{
byte* pbuff = (byte*)buff.ToPointer();
int loc = y * Stride + x * 4;
c = Color.FromArgb(pbuff[loc + 3], pbuff[loc + 2], pbuff[loc + 1], pbuff[loc]);
}
return c;
}
}
}
You should implement IScrollInfo on your canvas (or actually, create a custom Panel that inherits from Canvas and implements IScrollInfo).
That interface holds all that is relevant to your situation:
http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.iscrollinfo.aspx
http://blogs.msdn.com/b/jgoldb/archive/2008/03/08/performant-virtualized-wpf-canvas.aspx
i first sorry about my english,i´ll try to explain want i want to do
i need to draw an ellipse with wpf that represents an aura and it´s "deformations" representing problematic zones in it,in short an ellipse that can be deformed in running time in specific points
I'm trying to draw several bezier curves forming an ellipse but it´t very difficult (and i don´t know how) to make points that can be dragged forming convex or hollow zones in that ellipse.
¿i made myselft clear in my spanglish? ¿is there an easy way to do that?
Thanks in advance
I don't know what exactly you are trying to do but I recommend making a high resolution version of the ellipse and keeping track of the deformations yourself. Here is a sample program to get you started.
For this demo the XAML is simple:
<Canvas Name="canvas" Focusable="True" KeyDown="canvas_KeyDown" MouseDown="canvas_MouseDown" MouseMove="canvas_MouseMove" MouseUp="canvas_MouseUp"/>
and a the code-behind:
public partial class EllipseDemo : Window
{
const int resolution = 1000;
const double major = 150;
const double minor = 100;
const double xOrigin = 200;
const double yOrigin = 200;
const double radius = 10;
const double scale = 0.1;
const double spread = 10;
const double magnitude = 10;
Path path;
Ellipse controlPoint;
LineSegment[] segments;
double[] deformation;
double[] perturbation;
int controlPointIndex;
public EllipseDemo()
{
InitializeComponent();
segments = new LineSegment[resolution];
deformation = new double[resolution];
perturbation = new double[resolution];
for (int i = 0; i < resolution; i++)
{
var x = i >= resolution / 2 ? i - resolution : i;
perturbation[i] = magnitude * Math.Exp(-Math.Pow(scale * x, 2) / spread);
}
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 5;
CalculateEllipse();
canvas.Children.Add(path);
controlPoint = new Ellipse();
controlPoint.Stroke = new SolidColorBrush(Colors.Red);
controlPoint.Fill = new SolidColorBrush(Colors.Transparent);
controlPoint.Width = 2 * radius;
controlPoint.Height = 2 * radius;
MoveControlPoint(0);
canvas.Children.Add(controlPoint);
canvas.Focus();
}
void CalculateEllipse()
{
for (int i = 0; i < resolution; i++)
{
double angle = 2 * Math.PI * i / resolution;
double x = xOrigin + Math.Cos(angle) * (major + deformation[i]);
double y = yOrigin + Math.Sin(angle) * (minor + deformation[i]);
segments[i] = new LineSegment(new Point(x, y), true);
}
var figure = new PathFigure(segments[0].Point, segments, true);
var figures = new PathFigureCollection();
figures.Add(figure);
var geometry = new PathGeometry();
geometry.Figures = figures;
path.Data = geometry;
}
void MoveControlPoint(int index)
{
controlPointIndex = index;
Canvas.SetLeft(controlPoint, segments[index].Point.X - radius);
Canvas.SetTop(controlPoint, segments[index].Point.Y - radius);
}
bool mouseDown;
void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver != controlPoint)
return;
mouseDown = true;
controlPoint.CaptureMouse();
}
void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!mouseDown)
return;
int index = FindNearestIndex(e.GetPosition(canvas));
MoveControlPoint(index);
}
void canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!mouseDown)
return;
controlPoint.ReleaseMouseCapture();
mouseDown = false;
}
private void canvas_KeyDown(object sender, KeyEventArgs e)
{
int delta = 0;
switch (e.Key)
{
case Key.Up:
delta = 1;
break;
case Key.Down:
delta = -1;
break;
}
if (delta == 0)
return;
int index = controlPointIndex;
for (int i = 0; i < resolution; i++)
deformation[(i + index) % resolution] += delta * perturbation[i];
CalculateEllipse();
MoveControlPoint(index);
}
int FindNearestIndex(Point point)
{
var min = double.PositiveInfinity;
var index = -1;
for (int i = 0; i < segments.Length; i++)
{
var vector = point - segments[i].Point;
var distance = vector.LengthSquared;
if (distance < min)
{
index = i;
min = distance;
}
}
return index;
}
}
This works mostly with a Path represented by line segments and an Ellipse as a control point. The mouse can move the control point around the ellipse and then the arrow keys add or remove a canned perturbation. Everything is hard coded but if you are OK with the math then it should help you get started.
Here's the program in action: