Combining two existing annotation elements (PDFTron Net SDK) - c#

I am trying to create a custom annotation element combining an arrow and a freetext box element. The Arrow element i would like it to have its default behavior as originally assigned in the SDK, the freetext box though I would like to have a default size, predefined text in it and follow the arrow tail as soon as the arrow is moved or resized (selection, scaling transformation of the textbox I would like to disable it). Currently I have managed to draw an arrow with a free text element appearing on it's tail, but I am not aware of how I could make that free text element follow the arrow tail when its position changes and disable all its functionality (selection, scaling transformation, text input, etc...). Is there a way to group two existing annotation elements into one, or is there another easier approach of creating an arrow with textbox in its' tail including predefined text? Thank you in advance.

The code below shows how to add text to the default appearance that PDFNet will generate. Essentially you are decorating the default appearance.
The best thing to do is use our default appearance, and then overlay with your own content.
After calling Annot.RefreshAppearance, you would call something like the following.
static public void AddDecorations(Annots.Line line, PDFDoc doc)
{
ElementReader reader = new ElementReader();
ElementWriter writer = new ElementWriter();
ElementBuilder builder = new ElementBuilder();
writer.Begin(doc); // start new content stream
SDF.Obj old_app_stm = line.GetAppearance();
reader.Begin(old_app_stm);
Element element;
// isolate PDFNet default appearance in group
writer.WriteElement(builder.CreateGroupBegin());
while ((element = reader.Next()) != null)
{
writer.WriteElement(element);
}
element = builder.CreateGroupEnd();
writer.WriteElement(element);
/////////////////////////////////////////////////////
// Create matrix to position and rotate new text
Point start_pt = line.GetStartPoint();
Point end_pt = line.GetEndPoint();
double xDiff = end_pt.x - start_pt.x;
double yDiff = end_pt.y - start_pt.y;
double angle = Math.Atan2(yDiff, xDiff);
Matrix2D mtx = Matrix2D.RotationMatrix(-angle);
mtx.m_h = start_pt.x;
mtx.m_v = start_pt.y;
/////////////////////////////////////////////////////
element = builder.CreateTextBegin(Font.Create(doc, Font.StandardType1Font.e_helvetica_bold), 8);
writer.WriteElement(element);
element = builder.CreateTextRun(String.Format("{0}", line.GetSDFObj().GetObjNum()));
element.SetTextMatrix(mtx);
writer.WriteElement(element);
Rect new_bbox = new Rect();
element.GetBBox(new_bbox);
element = builder.CreateTextEnd();
writer.WriteElement(element);
// update bounding boxes
Rect old_bbox = new Rect(old_app_stm.FindObj("BBox"));
old_bbox.Normalize(); // make sure x1,y1 is bottom left
new_bbox.Normalize();
new_bbox = new Rect(Math.Min(new_bbox.x1, old_bbox.x1), Math.Min(new_bbox.y1, old_bbox.y1), Math.Max(new_bbox.x2, old_bbox.x2), Math.Max(new_bbox.y2, old_bbox.y2));
SDF.Obj new_app_stm = writer.End();
new_app_stm.PutRect("BBox", new_bbox.x1, new_bbox.y1, new_bbox.x2, new_bbox.y2);
line.SetRect(new_bbox);
line.SetAppearance(new_app_stm);
}
In summary, you wrap the pre-existing drawing elements in a group, and then write your own new content. Then update the bounding box rectangle, and update the appearance stream and annotation bounding boxes.

Related

AvalonEdit: Getting Visual Position for IBackgroundRenderer

In my AvalonEdit-based document editor, I am trying to add marker lines to the text view to indicate folds in the document, in a similar way to how Visual Studio joins the start and end of code block braces with dotted lines.
I have something that produces the correct result if the document is scrolled to the very top, but it doesn't update correctly if the document is scrolled down. Specifically, the lines are drawn as if the text view wasn't scrolled at all, and was still at the top of the document. I suspect the problem has something to do with the TextViewPosition and GetVisualPosition lines, but I don't understand how to correctly get the adjusted visual position with scrolling.
(To be clear, I have checked, and the Draw method is being called at the appropriate times to update the background, it's just that scrolling isn't accounted for when it does)
What I have so far, is the following, on a class which implements IBackgroundRenderer:
public void Draw(TextView textView, DrawingContext drawingContext) {
if (textView == null) { throw new ArgumentNullException("textView"); }
if (drawingContext == null) { throw new ArgumentNullException("drawingContext"); }
if (!textView.VisualLinesValid) { return; }
ReadOnlyCollection<VisualLine> visualLines = textView.VisualLines;
if (visualLines.Count == 0) { return; }
foreach (FoldingSection fold in foldingManager.AllFoldings.Where(f => !f.IsFolded)) {
DocumentLine startLine = textView.Document.GetLineByOffset(fold.StartOffset);
ISegment whitespace = TextUtilities.GetLeadingWhitespace(textView.Document, startLine);
if(whitespace.Length == 0) { continue; }
DocumentLine endLine = textView.Document.GetLineByOffset(fold.EndOffset);
TextLocation foldStart = textView.Document.GetLocation(whitespace.EndOffset);
TextLocation foldEnd = textView.Document.GetLocation(textView.Document.GetOffset(endLine.LineNumber, foldStart.Column));
// I am unsure exactly what TextViewPosition is meant to represent, in contrast to TextLocation
TextViewPosition startViewPos = new TextViewPosition(foldStart);
TextViewPosition endViewPos = new TextViewPosition(foldEnd);
// These lines are definitely not returning what I expect
Point foldStartPos = textView.GetVisualPosition(startViewPos, VisualYPosition.LineBottom);
Point foldEndPos = textView.GetVisualPosition(endViewPos, VisualYPosition.LineBottom);
Brush brush = new SolidColorBrush(LineColor);
brush.Freeze();
Pen dashPen = new Pen(brush, 0.5) { DashStyle = new DashStyle(new double[] { 2, 2 }, 0) };
dashPen.Freeze();
// New point created to avoid issues with nested folds causing slanted lines
drawingContext.DrawLine(dashPen, foldStartPos, new Point(foldStartPos.X, foldEndPos.Y));
}
}
The folding for the document is based on whitespace (very similar to Python-style indentation), hence the use of the leading whitespace for finding the column.
In short, how does one get the properly adjusted visual position from the document line number and column?
GetVisualPosition is documented as:
Returns: The position in WPF device-independent pixels relative to the top left corner of the document.
To use it for painting, you'll want to subtract the scroll position from it:
Point foldStartPos = textView.GetVisualPosition(startViewPos, VisualYPosition.LineBottom);
Point foldEndPos = textView.GetVisualPosition(endViewPos, VisualYPosition.LineBottom);
foldStartPos -= textView.ScrollOffset;
foldEndPos -= textView.ScrollOffset;
As for TextLocation vs. TextViewPosition: there are some cases where there are multiple possible locations that map to the same TextLocation.
There might be custom VisualLineElements with a documentLength of 0.
Or maybe word-wrap is enabled and a line is wrapped at a position where there is no space character: then both the end of the first TextLine and the beginning of the second TextLine refer to the same position in the document.
A TextViewPosition carries some extra information that allows distinguishing these cases. This is mostly important for the caret, so that clicking somewhere places the caret in the clicked position; not another equivalent position.

PointToScreen not returning screen coordinates

I am using EyeShot 12. I am creating a rectangle using EyeShot Line Entity, it has 2 dimensions along length and breadth.
My functionality involves changing the Dimension Text by using the action->SelectByPick, then picking anyone of the dimension and changing its value by bringing up a TextBox so that user can add the value. Here the TextBox pops-up on the location of mouse pointer.
Going further I click on Tab (keypad button) to switch to next dimension and also making sure that particular Dimension gets highlighted. But my concern is I am unable to locate the TextBox next to that highlighted dimension.
I am able to locate the position of existing Line(corresponding to the selected dimension) in Eyeshot coordinates but TextBox requires screen coordinates value for Positioning it exactly.
So I am using control.PointToScreen to convert eyeshot coordinates into screen but it return a Point which is same as to the Eyeshot coordinates.
code:
foreach (Entity ent in model1.Entities)
{
if (ent.Selected)
{
Line lin = (Line)ent;
Point3D midpt = lin.MidPoint;
string newpt1X = midpt.X.ToString();
string newpt1Y = midpt.Y.ToString();
System.Drawing.Point startPtX = model1.PointToScreen(new
System.Drawing.Point(int.Parse(newpt1X) + 20, int.Parse(newpt1Y) + 20));
TextBox tb = new TextBox();
tb.Text = "some text";
tb.Width = 50;
tb.Location = startPtX;
model1.Controls.Add(tb);
}
I looked for other results but everyone triggers to PointToScreen to get this convertion.
Hoping somebody can point what I am doing.
Thanks in advance
Suraj
You made your object (TextBox) a child of the ViewportLayout therefore you need the point relative to it. But the controls are not in the world coordinate but screen coordinate based on their parent.
What you actually need is two (2) conversion.
// first grab the entity point you want
// this is a world point in 3D. I used your line entity
// of your loop here
var entityPoint = ((Line)ent).MidPoint;
// now use your Viewport to transform the world point to a screen point
// this screen point is actually a point on your real physical monitor(s)
// so it is very generic, it need further conversion to be local to the control
var screenPoint = model1.WorldToScreen(entityPoint);
// now create a window 2d point
var window2Dpoint = new System.Drawing.Point(screenPoint.X, screenPoint.Y);
// now the point is on the complete screen but you want to know
// relative to your viewport where that is window-wise
var pointLocalToViewport = model1.PointToClient(window2Dpoint);
// now you can setup the textbox position with this point as it's local
// in X, Y relative to the model control.
tb.Left = pointLocalToViewport.X;
tb.Top = pointLocalToViewport.Y;
// then you can add the textbox to the model1.Controls

how to avoid blank area treeview node clicking?

I am working on a regular WinForm Outlook Addin and I have created a Treeview using DrawNode event, The tree is working as expected but there is a glitch in node clicking, only the green region is clickable and the half node gets non responsive.
When I use the MouseDown event the whole area gets clickable including the blank space right next to the node. But to restrict this blank clicking I am using a logic with the help of TreeViewHitTestLocations I am checking if the clicked location is the RightOfLabel then don't do anything but unfortunately this doesn't give me a precise result, it somehow gets confuse and takes right half of the label(Node) as the blank space and doesn't get clicked.
Note: I think this all happened because I played with DrawNode method and while keeping the distance between label and workspace icon the application underneath assumes that the label gets finish within the green portion so the red portion gets left as a blank space. This is just my assumption based on all the naïve things I have done with the method.
Need help to resolve this issue if someone can guide me to a fix. Thanks
void treeview_mousedown(object sender, MouseEventArgs e)
{
TreeNode nodeClicked;
// if arrow up/down will be excluded from the mousedown event
var hitTest = this.HitTest(e.Location);
if (hitTest.Location == TreeViewHitTestLocations.PlusMinus)
return;
if (hitTest.Location == TreeViewHitTestLocations.RightOfLabel)
return;
// Get the node clicked on
nodeClicked = this.GetNodeAt(e.X, e.Y);
// Was the node clicked on?
if (!(nodeClicked == null))
this.SelectedNode = nodeClicked;
}
Below is the treeview drawnode method I am using:
void treeview_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
Rectangle nodeRect = e.Node.Bounds;
// below location is the expand and collapse icon location
Point ptExpand = new Point(nodeRect.Location.X - 7, nodeRect.Location.Y + 5);
Image expandImg = null;
// check the below condition for nodes with child nodes and nodes without child nodes
if ( e.Node.Nodes.Count < 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.search;
else if (e.Node.IsExpanded && e.Node.Nodes.Count > 1)
expandImg = global::myresource.OfficeAddin.Controls.Resource.down_arrow_icon;
else
expandImg = global::myresource.OfficeAddin.Controls.Resource.right_arrow_icon;
Graphics g = Graphics.FromImage(expandImg);
IntPtr imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(expandImg, ptExpand);
// draw node icon
Point ptNodeIcon = new Point(nodeRect.Location.X - 4, nodeRect.Location.Y + 2);
Image nodeImg = global::myresource.OfficeAddin.Controls.Resource.folder_icon_16px;
g = Graphics.FromImage(nodeImg);
imgPtr = g.GetHdc();
g.ReleaseHdc();
e.Graphics.DrawImage(nodeImg, ptNodeIcon);
// draw node text
Font nodeFont = e.Node.NodeFont;
if (e.Node.NodeFont != null)
{
nodeFont = e.Node.NodeFont;
} else {
nodeFont = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
}
// set the forecolor
Color forecolor = e.Node.ForeColor;
// color same as the font color
string strSelectedColor = #"#505050";
Color selectedColor = System.Drawing.ColorTranslator.FromHtml(strSelectedColor);
SolidBrush selectedTreeBrush = new SolidBrush(selectedColor);
//Inflate to not be cut
Rectangle textRect = nodeRect;
//below value controls the width of the text if given less then, long texts will come in multiple lines
textRect.Width += 150;
// below value controls the over all width of the node, if given less all the things will get sqeeze
e.Graphics.DrawString(e.Node.Text, nodeFont, selectedTreeBrush , Rectangle.Inflate(textRect, -20, 0));
}
Your assumption is right, you can check it by marking the bounds at drawing, for example:
TreeNodeStates state = e.State;
bool isFocused = (state & TreeNodeStates.Focused) == TreeNodeStates.Focused;
if (isFocused)
ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds, foreColor, backColor);
Your code has more problems:
Issue 1: Images
If you use icons for nodes use the TreeView.ImageList and TreeNode.ImageKey properties. Otherwise, no space will be allocated for the image at drawing. In this case you can use TreeViewDrawMode.OwnerDrawText DrawMode.
Issue 2: Fonts
Do not use other font than the font of the node or the tree because it can have unexpected size. If this SegoeUI font is a default one use it at your TreeView instance instead. Then you can obtain the required font like this:
Font font = e.Node.NodeFont ?? e.Node.TreeView.Font;
Issue 3: Texts
You use Graphics.DrawString for drawing texts, which uses GDI+. However, starting with .NET 2.0 the default text rendering method is by GDI, unless CompatibleTextRendering property of a control is true. They produce slightly different text sizes.
To use GDI, for which the size of a label is calculated use the TextRenderer class instead:
TextRenderer.DrawText(e.Graphics, e.Node.Text, font, e.Bounds, foreColor, TextFormatFlags.GlyphOverhangPadding | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix);

ArcObjects 10.3 Add Transparent Polygon to Map

This is an issue I have been trying to tackle for a while and decided to reach out for help. I am creating an ESRI ArcGIS Desktop Add-In that allows the user to draw a polygon and then have it added to the map. I am able to capture the polygon and add it to the map, the issue is the transparency. Currently and by default it is 100% opacity and solid. I want to make it around 50% opacity so the user can see the data behind it.
Here is the code I have so far:
public static void AddPolygonToMap(IActiveView ActiveViewInstance, IGeometry NewGeo)
{
//Local Variable Declaration
var fillShapeElement = default(IFillShapeElement);
var element = default(IElement);
var graphicsContainer = default(IGraphicsContainer);
var simpleFilleSymbol = default(ISimpleFillSymbol);
var newRgbColor = default(IRgbColor);
var lineSymbol = default(ILineSymbol);
//Use the IElement interface to set the Envelope Element's geo
element = new PolygonElement();
element.Geometry = NewGeo;
//QI for the IFillShapeElement interface so that the symbol property can be set
fillShapeElement = element as IFillShapeElement;
//Create a new fill symbol
simpleFilleSymbol = new SimpleFillSymbol();
//Create a new color marker symbol of the color black;
newRgbColor = new RgbColor();
newRgbColor.Red = 0;
newRgbColor.Green = 0;
newRgbColor.Blue = 0;
//Create a new line symbol so that we can set the width outline
lineSymbol = new SimpleLineSymbol();
lineSymbol.Color = newRgbColor;
lineSymbol.Width = 2;
//Setup the Simple Fill Symbol
simpleFilleSymbol.Color = newRgbColor;
simpleFilleSymbol.Style = esriSimpleFillStyle.esriSFSHollow;
simpleFilleSymbol.Outline = lineSymbol;
fillShapeElement.Symbol = simpleFilleSymbol;
//QI for the graphics container from the active view allows access to basic graphics layer
graphicsContainer = ActiveViewInstance as IGraphicsContainer;
//Add the new element at Z order 0
graphicsContainer.AddElement((IElement)fillShapeElement, 0);
//Show the new graphic
ActiveViewInstance.Refresh();
}
I know that this is possible somehow and I am sure it's just a line or two missing but any help would be much appreciated.
V/r,
Josh
This looks to be a graphic element that you are creating. Graphic elements do not support transparency other than 100% transparent or 0% transparent. This is outlined in the following documentation:
IColor.Transparency Property
http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#//001w000000nt000000
For graphic elements, 0 for transparent and 255 for opaque are the only supported values.
I hope this helps!

How to move a WPF shape using an animation

I want to move a shape using an animation and am currently using the following code. However this does not result in the object actually moving, it just seems to change the location the object is rendered in, which is expected given we are setting the crossHair.RenterTransform.
EDIT:
To clarify - I am using the animation to simulate what an input file instructions contain and as a result think I can only build the animations in code when parsing the input files. I could be wrong about this but can't see how one could do this in XAML. The input file format is not in XAML.
Because there are a number of sequential moves contained in the input file I an currently using the shapes current position as the start point for the next animation, however this does not work because it seems the animation is not actually moving the shape.
As a work around I am now changing the shapes actual position in the animations completion handler. This seems to be working.
So the question remains as to how can I use the same transform to actually move the shape rather than simply rendering it in a different position ?
private Storyboard MoveCrossHairToPoint(double x, double y)
{
// Adjust for crosshair size so its centered on the x
double xPos = x;
double yPos = y;
double xStart = Canvas.GetLeft(crossHair)+crossHair.Width/2;
double yStart = Canvas.GetTop(crossHair)+crossHair.Height/2;
// Create a NameScope for the page so that 
// we can use Storyboards.
NameScope.SetNameScope(this, new NameScope());
// Create a MatrixTransform. This transform 
// will be used to move the crossHair.
MatrixTransform crossHairMatrixTransform = new MatrixTransform();
crossHair.RenderTransform = crossHairMatrixTransform;
// Register the transform's name with the page 
// so that it can be targeted by a Storyboard. 
this.RegisterName("MoveCrossHairMatrixTransform", crossHairMatrixTransform);
// Create the animation path.
PathGeometry animationPath = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = new Point(xStart, yStart);
LineSegment lineSegment = new LineSegment(new Point(x, y),true);
pFigure.Segments.Add(lineSegment);
animationPath.Figures.Add(pFigure);
// Create a path to follow
Path path = new Path();
path.Data = animationPath;
path.Stroke = System.Windows.Media.Brushes.Green;
this.bedCanvas.Children.Add(path);
// Freeze the PathGeometry for performance benefits.
animationPath.Freeze();
// Create a MatrixAnimationUsingPath to move the 
// button along the path by animating 
// its MatrixTransform.
MatrixAnimationUsingPath matrixAnimation =
new MatrixAnimationUsingPath();
matrixAnimation.PathGeometry = animationPath;
double time = GetTimeForVelocityOverPath(animationPath, this.velocityMove);
matrixAnimation.Duration = TimeSpan.FromSeconds(time);
//matrixAnimation.RepeatBehavior = RepeatBehavior.;
// Set the animation's DoesRotateWithTangent property 
// to true so that rotates the rectangle in addition 
// to moving it.
matrixAnimation.DoesRotateWithTangent = false;
// Set the animation to target the Matrix property 
// of the MatrixTransform named "ButtonMatrixTransform".
Storyboard.SetTargetName(matrixAnimation, "MoveCrossHairMatrixTransform");
Storyboard.SetTargetProperty(matrixAnimation,
new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.Children.Add(matrixAnimation);
return pathAnimationStoryboard;
}

Categories

Resources