Adding Text inside a Shape - wpf

I have a custom WPF control MyLine that should represent or not some text in its middle.
public class MyLine : Shape
{
public double X1, Y1, X2, Y2;
public bool IsTextDisplayed;
public string Caption;
protected override System.Windows.Media.Geometry DefiningGeometry
{
get
{
var geometryGroup = new GeometryGroup();
if (IsTextDisplayed)
{
// calculate text point
var midPoint = new Point((X1 + X2) / 2.0, (Y1 + Y2) / 2.0);
// add a TextBlock with the Caption text in that point
// ???
}
// Add line
geometryGroup.Children.Add(new LineGeometry(
new Point(X1, Y1), new Point(X2, Y2)));
return geometryGroup;
}
}
}
How should I add the a TextBlock (or Label) here?
I tried to add a FormattedText inside, but this is NOK, because it draws the text with the line fat brush and is impossible to read something.
EDIT
Adding a visual Child
public MyLine() : base()
{
textBlock = new System.Windows.Controls.TextBlock();
textBlock.Visibility = System.Windows.Visibility.Hidden;
this.AddVisualChild(textBlock);
}
protected override System.Windows.Media.Geometry DefiningGeometry
{
get
{
...
if (IsTextDisplayed)
{
var midPoint = new Point((X1 + X2) / 2.0, (Y1 + Y2) / 2.0);
string text = "some custom text";
Canvas.SetLeft(textBlock, midPoint.X);
Canvas.SetBottom(textBlock, midPoint.Y);
textBlock.Text = text;
this.textBlock.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.textBlock.Visibility = System.Windows.Visibility.Hidden;
}
I don't see any label... :"/
EDIT2
Adding Adorner
public MyLine() : base()
{
this.Loaded += new RoutedEventHandler(MyLine_Loaded);
}
void MyLine_Loaded(object sender, RoutedEventArgs e)
{
AdornerLayer aLayer = AdornerLayer.GetAdornerLayer(this);
if (aLayer != null)
aLayer.Add(new TextAdorner(this));
}
class TextAdorner : Adorner
{
public TextAdorner(UIElement adornedElement) : base(adornedElement)
{ }
protected override void OnRender(DrawingContext drawingContext)
{
MyLine segment = (this.AdornedElement as MyLine);
if (segment != null && segment.IsLabelUsed)
{
Rect segmentBounds = new Rect(segment.DesiredSize);
FormattedText ft = new FormattedText(
"654 m", Thread.CurrentThread.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface("Arial"), 12, Brushes.White);
drawingContext.DrawText(ft, segmentBounds.BottomRight);
}
}
}
Now, apparently the code never enters in the OnRender adorner method...

If you don't want the text displayed in the same brush as the line, you probably don't want to use a Shape as the base class, as the Geometry returned from DefiningGeometry is rendered in one brush. If you really want to use a Shape as the base class, you probably want to add the text as a visual child rather than add it to the existing Geometry.
If you aren't going to be using a lot of these, and can afford a slightly heavy-weight container, I'd suggest simply creating a UserControl based control that contains this Shape you created, and a text element like TextBox on a Canvas (for absolute positioning). Again, this is not going to be a great solution if you have hundreds or thousands of these, but if you only have tens of them, it is probably the easiest and quickest solution.

I'd use an adorner to draw the text on top of the line.
http://msdn.microsoft.com/en-us/library/ms746703.aspx

Related

How can I find UIElements in a rectangle in WPF?

I need to find UIElements in (rectangle/area/bounds).
MainWindow I'm doing the following:
I register the mouse down as the start position.
I regsiter the mouse up position.
Now I need to find ll (buttons, textboxes, etc) in the rectangle between start
postion and the end position.
I found in the msdn the HitTest approach but it is only for one point. I think, walking through all points in the founded
rectangle it is a performance disaster.
http://msdn.microsoft.com/en-us/library/ms752097.aspx
My code based on MVVM pattern:
private ObservableCollection<UIElementViewModel> wells;
private Point stratPoint; // Mouse down
public ICommand MouseUpRightCommand
{
get
{
if (this.mouseUpRightCommand == null)
{
this.mouseUpRightCommand = new RelayCommands(
param =>
{
if (param is MouseButtonEventArgs)
{
var e = (param as MouseButtonEventArgs);
//Set the end point
endPosition = e.GetPosition(((ItemsControl)e.Source));
// for example, here I want to find all controls(UIElements) in the
// founded rectangle of stratPoint and endPosition.
}
});
}
return this.mouseUpRightCommand;
}
}
Any other idea or a better approach?
Thanks
I would use FrameworkElement (which extends UIElement) instead of UIElement, in order to use ActualWidth and ActualHeight properties
Then create a static class which does some math MouseUtils
with those static fields
private static double _dContainerTop;
private static double _dContainerBottom;
private static double _dContainerLeft;
private static double _dContainerRight;
private static double _dCursorTop;
private static double _dCursorLeft;
private static double _dCursorRight;
private static double _dCursorBottom;
and those static methods
private static void FindValues(FrameworkElement element, Visual rootVisual)
{
var containerTopLeft = container.TransformToAncestor(rootVisual).Transform(new Point(0, 0));
_dContainerTop = containerTopLeft.Y;
_dContainerBottom = _dContainerTop + container.ActualHeight;
_dContainerLeft = containerTopLeft.X;
_dContainerRight = _dContainerLeft + container.ActualWidth;
}
and
public static bool IsElementUnderRectCursor(FrameworkElement element, Point startPoint, Point endPoint, Visual rootVisual)
{
_dCursorTop=Math.Min(startPoint.Y, endPoint.Y);
_dCursorBottom=Math.Max(startPoint.Y, endPoint.Y);
_dCursorLeft=Math.Min(startPoint.X, endPoint.X);
_dCursorRight=Math.Max(startPoint.X, endPoint.X);
FindValues(container, rootVisual);
if (_dContainerTop < _dCursorTop|| _dCursorBottom< _dContainerBottom )
{
return false;
}
if (_dContainerLeft < _dCursorLeft|| _dContainerRight < _dCursorRight)
{
return false;
}
return true;
}
Rootvisual being your window for example;
Then loop over ObservableCollection<FrameworkElement> wells and call that function IsElementUnderRectCursor.
This is inspired from:
Kinecting the Dots
Astreal thanks again for your answer. It's done. I just moved the selection code from modelView to view. The selection done only in the UI.
private void SelectWells(RectangleGeometry selectionRectangle, FrameworkElement frameworkElement)
{
var items = GetItemsControl(frameworkElement);
foreach (var item in items.Items)
{
var viusalItem = (ContentPresenter)items.ItemContainerGenerator.ContainerFromItem(item);
var wellControl = this.GetWellControl(viusalItem);
var relativePoint = wellControl.TransformToAncestor(items).Transform(new Point(0, 0));
var controlRectangle =
new RectangleGeometry(
new Rect(relativePoint.X, relativePoint.Y, wellControl.ActualWidth, wellControl.ActualHeight));
var intersectionGeometry = Geometry.Combine(
selectionRectangle, controlRectangle, GeometryCombineMode.Intersect, null);
if (intersectionGeometry.GetArea() > 0)
{
wellControl.Command.Execute(this);
}
}
}
usefull link for u:
http://www.codeproject.com/Articles/354853/WPF-Organization-Chart-Hierarchy-MVVM-Application
When an user clicks on a node in the tree we need to let the ViewModel node know that the selection has changed. We like to route the event as a command to the ViewModel

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)

Ellipse Geometry Custom Shape

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

Add some text in a WPF shape

I have a custom WPF control MyLine that should represent or not some text in its middle.
public class MyLine : Shape
{
public double X1, Y1, X2, Y2;
public bool IsTextDisplayed;
public string Caption;
protected override System.Windows.Media.Geometry DefiningGeometry
{
get
{
var geometryGroup = new GeometryGroup();
if (IsTextDisplayed)
{
// calculate text point
var midPoint = new Point((X1 + X2) / 2.0, (Y1 + Y2) / 2.0);
// add 'Caption' text in that point
// ???
}
// Add line
geometryGroup.Children.Add(new LineGeometry(
new Point(X1, Y1), new Point(X2, Y2)));
return geometryGroup;
}
}
}
So, how should I add the text here?
Create a FormattedText object and then create a Geometry from it:
FormattedText ft = new FormattedText(
"Caption",
Thread.CurrentThread.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface("Verdana"), 32, Brushes.Black);
Geometry geometry = ft.BuildGeometry(midpoint);
geometryGroup.Children.Add(geometry);

WPF: Drawing on top of a TextBlock

I want to be able to draw on to the top of a TextBlock, and have found a way to do this, but i cannot remove the drawing once it is there. Here is the code.
public class DerivedTextBlock : TextBlock {
public Boolean DrawExtra {
get { return (Boolean)GetValue(DrawExtraProperty); }
set { SetValue(DrawExtraProperty, value); }
}
// Using a DependencyProperty as the backing store for DrawExtra. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DrawExtraProperty =
DependencyProperty.Register("DrawExtra", typeof(Boolean), typeof(DerivedTextBlock), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsArrange));
public DrawingVisual DrawingVisual { get; set; }
public DerivedTextBlock() {
DrawingVisual = this.CreateDrawingVisualRectangle();
}
protected override int VisualChildrenCount {
get {
//if we want to draw our extra info, add one to
// our visualChildrenCount, usually with a textblock it is 0
if (DrawExtra) {
return base.VisualChildrenCount + 1;
}
else {
return base.VisualChildrenCount;
}
}
}
protected override Visual GetVisualChild(int index) {
return DrawingVisual;
}
// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle() {
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new Point(10.0, 0), new Size(10.0 / 2.0, 10));
drawingContext.DrawRectangle(Brushes.LightBlue, (Pen)null, rect);
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
}
Reason I want to do this: We have a datagrid with a lot of cells, each cell displaying text. we show some validation information on the cells and we do this by using a template with a textblock and some paths hosten in a grid. the overhead of this adds extra elements to the visual tree and when we have to redraw (on loading, switching windows or on a sort) it takes a lot longer the more elements in the visual tree. when it is just a textblock it is about 1/3 - 1/2 faster than having the control with a grid. So we would like to draw our validation stuff right on top of the textbox.
Your problems are:
GetVisualChild() should return base.GetVisualChild(index) except when index==base.VisualChildrenCount.
You forgot to call AddVisualChild() when DrawingExtra becomes true or DrawingVisual changes
You forgot to call RemoveVisualChild() when DrawingExtra becomes false or DrawingVisual changes
You can fix #2 and #3 by setting a PropertyChangedCallback on DrawingExtra and adding code to the setter of DrawingVisual.
Explanation: It is the AddVisualChild() call that actually adds the visual to the tree. What is happening is that your visual is being found and displayed "accidentally" because of your error in GetVisualChild(), but it is not being properly linked into the visual tree so you'll encounter many problems.
Update
I edited your code as described above, and it worked perfectly. Here are the changes:
...
{
PropertyChangedCallback = (obj, e) =>
{
var textBlock = (DerivedTextBlock)obj;
if((bool)e.OldValue) textBlock.RemoveVisualChild(textBlock.DrawingVisual);
if((bool)e.NewValue) textBlock.AddVisualChild(textBlock.DrawingVisual);
}
});
public DrawingVisual DrawingVisual
{
get { return _drawingVisual; }
set
{
if(DrawExtra) RemoveVisualChild(_drawingVisual);
_drawingVisual = value;
if(DrawExtra) AddVisualChild(_drawingVisual);
}
}
private DrawingVisual _drawingVisual;
...
protected override int VisualChildrenCount
{
get { return base.VisualChildrenCount + (DrawExtra ? 1 : 0); }
}
protected override Visual GetVisualChild(int index)
{
return index==base.VisualChildrenCount ? DrawingVisual : base.GetVisualChild(index);
}

Resources