Ellipse Geometry Custom Shape - wpf

I am deriving from shape to draw an ellipse. The drawing starts at 0,0 so only the bottom right corner of the ellipse its drawn. How do I transform the origin in the overridegeometry method:
class Ellipse2 : Shape
{
EllipseGeometry ellipse;
public static readonly DependencyProperty TextBoxRProperty = DependencyProperty.Register("TextBoxR", typeof(TextBox), typeof(Ellipse2), new FrameworkPropertyMetadata(null));
public TextBox TextBox
{
get { return (TextBox)GetValue(TextBoxRProperty); }
set { SetValue(TextBoxRProperty, value); }
}
public Ellipse2()
{
ellipse = new EllipseGeometry();
this.Stroke = Brushes.Gray;
this.StrokeThickness = 3;
}
protected override Geometry DefiningGeometry
{
get
{
ellipse.RadiusX = this.Width/2;
ellipse.RadiusY = this.Height/2;
return ellipse;
}
}
}

I fixed it by using
protected override Geometry DefiningGeometry
{
get
{
TranslateTransform t = new TranslateTransform(ActualWidth / 2, ActualHeight / 2);
ellipse.Transform = t;
ellipse.RadiusX = this.ActualWidth/2;
ellipse.RadiusY = this.ActualHeight/2;
return ellipse;
}
}
Another way would be to set the center property of the ellipse I think to the attributes (I haven't tried this yet).

Related

How to draw dropshadow effect in a geometry in WPF

I'm drawing the following Shape in a Canvas.
I would like to highlight it when it's selected by changing its color (the easy part) and drawing an small halo around it:
This is how I did using SASS: http://codepen.io/aaromnido/pen/zKvAwd/
How coud I draw in WPF? Remember that I'm drawing using the Shape's OnRender method.
Set some defaults in constructor.
One of these defaults is Shape.Effect, as it will be animated on MouseEnter event.
Construct VisualStates for Normal , and MouseEnter scenarios.
Change the VisualState of the element using VisualStateManager.GoToElementState() in MouseEnter and MouseLeave event handlers.
You can expose various properties using DPs for customization.
NewShape.cs
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media.Effects;
namespace WpfStackOverflow.NewShape
{
public class CNewShape : Shape
{
public CNewShape()
{
// setting the defaults
this.Width = 40;
this.Height = 40;
this.Stroke = new SolidColorBrush() { Color = Colors.Red };
this.StrokeThickness = 5;
this.Effect = new DropShadowEffect() {
Color = Colors.Transparent,
BlurRadius = 1,
Direction = -150,
ShadowDepth = 1
};
// constructing the VisualStates
_constructVisualStates();
// event handlers
this.MouseEnter += CNewShape_MouseEnter;
this.MouseLeave += CNewShape_MouseLeave;
}
#region EventHandlers
void CNewShape_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSNormal", false);
}
void CNewShape_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSMouseEnter", false);
}
#endregion
#region Overrides
// This needs to be implemented as it is abstract in base class
GeometryGroup geo = new GeometryGroup();
protected override Geometry DefiningGeometry
{
get { return geo; }
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(this.Stroke, StrokeThickness);
drawingContext.DrawEllipse(Brushes.Transparent, pen, new Point(Width/2, Height/2), 40, 40);
drawingContext.DrawEllipse(Stroke, null, new Point(Width / 2, Height / 2), 30, 30);
base.OnRender(drawingContext);
}
#endregion
#region Helpers
private void _constructVisualStates()
{
VisualStateGroup vsg1 = new VisualStateGroup();
#region VSNormal (Normal Visual State)
VisualState stateVSNormal = new VisualState() { Name = "VSNormal" };
Storyboard sbVSNormal = new Storyboard();
ObjectAnimationUsingKeyFrames oa = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(oa, new PropertyPath("Effect"));
DiscreteObjectKeyFrame dokf = new DiscreteObjectKeyFrame(null);
oa.KeyFrames.Add(dokf);
sbVSNormal.Children.Add(oa);
stateVSNormal.Storyboard = sbVSNormal;
vsg1.States.Add(stateVSNormal);
#endregion
#region VSMouseEnter (MouseEnter Visual State)
VisualState stateVSMouseEnter = new VisualState() { Name = "VSMouseEnter" };
Storyboard sbVSMouseEnter = new Storyboard();
ColorAnimation caStrokeColor = new ColorAnimation();
caStrokeColor.To = (Color)ColorConverter.ConvertFromString("#FF24BCDE");
Storyboard.SetTargetProperty(caStrokeColor, new PropertyPath("(Shape.Stroke).(SolidColorBrush.Color)"));
sbVSMouseEnter.Children.Add(caStrokeColor);
ColorAnimation caEffectColor = new ColorAnimation();
caEffectColor.To = (Color)ColorConverter.ConvertFromString("#FFA4E1F3");
Storyboard.SetTargetProperty(caEffectColor, new PropertyPath("(Shape.Effect).(Color)"));
sbVSMouseEnter.Children.Add(caEffectColor);
DoubleAnimation daBlurRadius = new DoubleAnimation();
daBlurRadius.To = 10;
Storyboard.SetTargetProperty(daBlurRadius, new PropertyPath("(Shape.Effect).(BlurRadius)"));
sbVSMouseEnter.Children.Add(daBlurRadius);
DoubleAnimation daDirection = new DoubleAnimation();
daDirection.To = -190;
Storyboard.SetTargetProperty(daDirection, new PropertyPath("(Shape.Effect).(Direction)"));
sbVSMouseEnter.Children.Add(daDirection);
stateVSMouseEnter.Storyboard = sbVSMouseEnter;
vsg1.States.Add(stateVSMouseEnter);
#endregion
VisualStateManager.GetVisualStateGroups(this).Add(vsg1);
}
#endregion
}
}
Usage
<local:CNewShape Canvas.Left="70" Canvas.Top="52" Stroke="#FF374095" StrokeThickness="10" Width="100" Height="100" />
Output
Quality of the image is bad. On screen actual output looks good.
Whatever your trigger is that your control enters the Highlighted state, in that trigger just set the Effect property. For my test the "trigger" is a property:
public static readonly DependencyProperty ShowShadowProperty =
DependencyProperty.Register ("ShowShadow", typeof (bool), typeof (TestShape), new PropertyMetadata (false, ShowShadowChanged));
public bool ShowShadow
{
get { return (bool)GetValue (ShowShadowProperty); }
set { SetValue (ShowShadowProperty, value); }
}
private static void ShowShadowChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestShape)d).OnShowShadow ();
}
private void OnShowShadow ()
{
if (ShowShadow)
{
Effect = new DropShadowEffect { Direction = 0, ShadowDepth = 20, BlurRadius = 33, Opacity = 1, Color = Colors.Black};
}
else
{
Effect = null;
}
}
Which means you don't need to do anything in OnRender.

Is a dashed border around a selected text/image element possible in Silverlight?

I have an image editor I'm developing in silverlight which has multiple text and image elements on one canvas, that are draggable etc. I need feedback for the user to highlight the selected element when it is clicked on by the user and highlight a different element instead if another is clicked. I think I should do this with a dashed border around the element, but I don't know if it's possible.
Below is my code relating to the elements -
Project.cs
namespace ImageEditor.Client.BLL
{
public class Project : INotifyPropertyChanged
{
private int numberOfElements;
#region Properties
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
elements = value;
NotifyPropertyChanged("Elements");
}
}
private FrameworkElement selectedElement;
public FrameworkElement SelectedElement
{
get { return selectedElement; }
set
{
selectedElement = value;
NotifyPropertyChanged("SelectedElement");
}
}
private TextBlock selectedTextElement;
public TextBlock SelectedTextElement
{
get { return selectedTextElement; }
set
{
selectedTextElement = value;
NotifyPropertyChanged("SelectedTextElement");
}
}
private Image selectedImageElement;
public Image SelectedImageElement
{
get { return selectedImageElement; }
set
{
selectedImageElement = value;
NotifyPropertyChanged("SelectedImageElement");
}
}
#endregion
#region Methods
private void AddTextElement(object param)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = "New Text";
textBlock.Foreground = new SolidColorBrush(Colors.Gray);
textBlock.FontSize = 25;
textBlock.FontFamily = new FontFamily("Arial");
textBlock.Cursor = Cursors.Hand;
textBlock.Tag = null;
AddDraggingBehavior(textBlock);
textBlock.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(textBlock);
numberOfElements++;
this.SelectedElement = textBlock;
this.selectedTextElement = textBlock;
}
private BitmapImage GetImageFromLocalMachine(out bool? success, out string fileName)
{
OpenFileDialog dialog = new OpenFileDialog()
{
Filter = "Image Files (*.bmp;*.jpg;*.gif;*.png;)|*.bmp;*.jpg;*.gif;*.png;",
Multiselect = false
};
success = dialog.ShowDialog();
if (success == true)
{
fileName = dialog.File.Name;
FileStream stream = dialog.File.OpenRead();
byte[] data;
BitmapImage imageSource = new BitmapImage();
using (FileStream fileStream = stream)
{
imageSource.SetSource(fileStream);
data = new byte[fileStream.Length];
fileStream.Read(data, 0, data.Length);
fileStream.Flush();
fileStream.Close();
}
return imageSource;
}
else
{
fileName = string.Empty;
return new BitmapImage();
}
}
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
if (gotImage == true)
{
Image image = new Image();
image.Name = fileName;
image.Source = imageSource;
image.Height = imageSource.PixelHeight;
image.Width = imageSource.PixelWidth;
image.MaxHeight = imageSource.PixelHeight;
image.MaxWidth = imageSource.PixelWidth;
image.Cursor = Cursors.Hand;
image.Tag = null;
AddDraggingBehavior(image);
image.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(image);
numberOfElements++;
this.SelectedElement = image;
this.SelectedImageElement = image;
}
}
private void OrderElements()
{
var elList = (from element in this.Elements
orderby element.GetValue(Canvas.ZIndexProperty)
select element).ToList<FrameworkElement>();
for (int i = 0; i < elList.Count; i++)
{
FrameworkElement fe = elList[i];
fe.SetValue(Canvas.ZIndexProperty, i);
}
this.Elements = new ObservableCollection<FrameworkElement>(elList);
}
public void element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.SelectedElement = sender as FrameworkElement;
if (sender is TextBlock)
{
this.SelectedTextElement = sender as TextBlock;
FadeOut(this.SelectedTextElement);
}
else if (sender is Image)
{
this.SelectedImageElement = sender as Image;
FadeOut(this.SelectedImageElement);
}
}
#endregion
More than needed there but you get a good idea of how it all works from that. How might I go about it? I'm still pretty new to silverlight
Edit:
This is my start attempt at a DashBorder Method, wherein I'm trying to make a rectangle the same dimensions as the selected element which will go around the element
public static void DashBorder(FrameworkElement element)
{
Rectangle rect = new Rectangle();
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.Width=element.Width;
rect.Height=element.Height;
rect.StrokeDashArray = new DoubleCollection() { 2, 2 };
}
It appears to do nothing and isn't what I want to do anyway. Is there no way to make a dash border on a FrameworkElement directly?
I don't know how, but google does.
You can use the StrokeDashArray to achieve the desired effect,
example:
<Rectangle Canvas.Left="10" Canvas.Top="10" Width="100" Height="100"
Stroke="Black" StrokeDashArray="10, 2"/>
The first number in StrokeDashArray is the length of the dash, the
second number is the length of the gap. You can repeat the dash gap
pairs to generate different patterns.
Edit:
To do this in code create a rectangle and set it's StrokeDashArray property like this (code untested):
Rectangle rect = new Rectangle();
rect.StrokeThickness = 1;
double[] dashArray = new double[2];
dashArray[0] = 2;
dashArray[1] = 4;
rect.StrokeDashArray = dashArray;

Displaying an 8x8 Grid in Windows 8 / WPF / Silverlight

I want to display an 8x8 Grid in Windows 8 metro app. To do this:
I created a Grid, and added 8 row definitions and 8 column definitions.
I then add a Rectangle with a black border to each of the grid cells.
Then in the MeasureOverride method, I check the availableSize. Since my grid needs to be square (aspect ratio = 1.0), I compute the minimum of availableSize.Width, availableSize.Height and return a new Size equal to (minimum, minimum).
However this does not work. The resulting grid's size is equal to availableSize, and not the size I return from my MeasureOverride method. If I modify the MeaureOverride, so that I set Height of RowDefinitions to minimum, and Width of ColumnDefinitions to minimum, then it works. But I saw some videos and they say you should not be explicitly setting Height & Width properties of anything.
So, is there a better way to accomplish what I want?
I'm not sure if you need to interact with those cells in any way, but if you just want to draw a grid, here is a quick control to do it. It will fill the space of the parent control.
public class GridShape : Control
{
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(GridShape), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(GridShape), new PropertyMetadata(1.0));
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(Stroke, StrokeThickness);
double heightSpan = ActualHeight / Rows;
double widthSpan = ActualWidth / Columns;
for (double y = 0; y <= ActualHeight; y += heightSpan)
drawingContext.DrawLine(pen, new Point(0, y), new Point(ActualWidth, y));
for (double x = 0; x <= ActualWidth; x += widthSpan)
drawingContext.DrawLine(pen, new Point(x, 0), new Point(x, ActualHeight));
}
}
One solution is to create a custom Grid control to handle the width, height
public class SquareGrid : Grid
{
public SquareGrid()
{
this.SizeChanged += OnSizeChanged;
this.Loaded += OnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
parent.SizeChanged += ParentOnSizeChanged;
}
private void ParentOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
FrameworkElement parent = sender as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void ResizeToSquare(FrameworkElement parent)
{
var min = Math.Min(parent.ActualHeight, parent.ActualWidth);
this.Width = min;
this.Height = min;
}
}
You could also build a Behavior for this that would do the same thing.

Adding VisualChild to Canvas does not work - not displayed

I have to write a class that inherits from Canvas and that is able to add Visuals to the canvasbase.
So I wrote this code:
class TestCanvas : Canvas
{
VisualCollection visuals;
public TestCanvas()
{
visuals = new VisualCollection(this);
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
Rectangle rect = new Rectangle
{
Width = 200,
Height = 200,
Stroke = Brushes.Red,
StrokeThickness = 5,
Fill = Brushes.Black
};
visuals.Add(rect);
base.OnMouseDown(e);
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index > visuals.Count)
throw new ArgumentOutOfRangeException("index");
return visuals[index];
}
}
But if I click on it and I add this Rectangle it is not displayed.
So does anyone has an idea why this does not work?
If you want to programatically add children to a derived Canvas, you can simply do it like this:
Rectangle rect = new Rectangle
{
Width = 200,
Height = 200,
Stroke = Brushes.Red,
StrokeThickness = 5,
Fill = Brushes.Black
};
Canvas.SetLeft(rect, ...);
Canvas.SetTop(rect, ...);
Children.Add(rect);
No need to go down to the Visual layer and override VisualChildrenCount and GetVisualChild.
If for any other reason you have to use Visuals, then there is no need to use a Canvas. You might derive from UIElement or FrameworkElement.

Convert WPF control in Silverlight

There is an very old WPF application of Hyper Tree - http://blogs.msdn.com/b/llobo/archive/2007/10/31/mindmap-app-using-hyperbolic-tree.aspx.
The source code can be found at codeplax.com -
http://hypertree.codeplex.com/releases/view/11524
I wanted to use this tree control in my silverlight application. Now the issue is that i am new to silverlight, and the code is using some WPF specific things.
Please suggest me to solve my problem.
Thanks in advance.
Abhinav
Update:
things like
FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions, InvalidateVisual(), OnRender override, child UIElements.
Code Added:
public class SmartBorder : Decorator
{
#region Dependency Properties
public static readonly DependencyProperty GlowBrushProperty =
DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(SmartBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
......
#region Dependency Property backing CLR properties
......
#endregion
// if the button is pressed, this fires
private static void OnRenderIsPressedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
if ((bool)e.NewValue == true)
{
border.BorderBrush = Brushes.Transparent;
border.BorderWidth = 2;
}
else
{
border.BorderBrush = Brushes.Red;
border.BorderWidth = 2;
}
border.InvalidateVisual();
}
}
// if the mouse is over the control, this fires
private static void OnRenderIsMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
border.InvalidateVisual();
}
}
// a series of methods which all make getting the default or currently selected brush easier
protected override void OnRender(DrawingContext dc)
{
Rect rc = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
LinearGradientBrush gradientOverlay = GetGradientOverlay();
Brush glowBrush = GetGlowBrush();
Brush backBrush = GetBackgroundBrush();
Brush borderBrush = GetBorderBrush();
Pen borderPen = new Pen(borderBrush, BorderWidth);
double cornerRadiusCache = CornerRadius;
// draw the highlight as necessary
if (RenderIsMouseOver)
{
Rect rcGlow = rc;
double glowMove = BorderWidth * 2;
rcGlow.Inflate(glowMove, glowMove);
glowMove = 0;
rcGlow.Offset(new Vector(glowMove, glowMove));
dc.DrawRoundedRectangle(GetOuterGlowBrush(), null, rcGlow, cornerRadiusCache, cornerRadiusCache);
}
// we want to clip anything that might errantly draw outside of the smart border control
dc.PushClip(new RectangleGeometry(rc, cornerRadiusCache, cornerRadiusCache));
dc.DrawRoundedRectangle(backBrush, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.DrawRoundedRectangle(gradientOverlay, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
if (!RenderIsPressed)
{
double clipBorderSize = BorderWidth * -4.0;
Rect rcClip = rc;
rcClip.Offset(clipBorderSize, clipBorderSize);
rcClip.Inflate(-clipBorderSize, -clipBorderSize);
dc.PushClip(new RectangleGeometry(rcClip, cornerRadiusCache, cornerRadiusCache));
dc.DrawEllipse(glowBrush, null, new Point(this.ActualWidth / 2, this.ActualHeight * 0.10), this.ActualWidth * 0.80, this.ActualHeight * 0.40);
dc.Pop();
}
// just draw the border now to make sure it overlaps everything nicely
dc.DrawRoundedRectangle(null, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.Pop();
//base.OnRender(drawingContext);
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = this.Child as UIElement;
double borderThickness = BorderWidth * 2.0;
if (child != null)
{
...
}
return new Size(Math.Min(borderThickness, constraint.Width), Math.Min(borderThickness, constraint.Height));
}
}
Regarding FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions and value coercions etc. for Silverlight see the WPF_Compatibility solution under the ClipFlair codebase (http://clipflair.codeplex.com)

Resources