Remove Specific LineGeometry in a Path - c#

My application consists of a canvas with some user drag-droppable UIelements which can be further connected using lines.
To connect two UIelements I have used a Path consisting a GeometryGroup, which further contains LineGeometry as its childs. Check this screenshot.
So as in the picture, the three items are connected via a Path which consists of 2 LineGeometry. I'm trying to implement 'Remove Link' option but all i can do is remove the whole Path, which would remove both the LineGeometry. How can i specifically select that particular line segment and remove it?

There is a solution without implementing math for hit testing. For example if you want to remove a line by mouse down:
<Canvas Mouse.MouseDown="Canvas_MouseDown">
where Canvas_MouseDown should be implemented like this:
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
var canvas = sender as Canvas;
if (canvas == null)
return;
// 1. Find a Path containing links
HitTestResult hitTestResult = VisualTreeHelper.HitTest(canvas, e.GetPosition(canvas));
var path = hitTestResult.VisualHit as Path;
if (path == null)
return;
// 2. Iterate through geometries of the Path and hit test each one
// to find a line to delete
var geometryGroup = path.Data as GeometryGroup;
if (geometryGroup == null)
return;
GeometryCollection geometries = geometryGroup.Children;
Point point = e.GetPosition(path);
var pen = new Pen(path.Stroke, path.StrokeThickness);
var lineToDelete = geometries.OfType<LineGeometry>()
.FirstOrDefault(l => l.StrokeContains(pen, point));
// 3. Delete link
if (lineToDelete != null)
geometries.Remove(lineToDelete);
}

You need the distances to the click point.
public void RemoveLink(Point point)
{
// ...
// point - 2D click point
// lineList - list of links (lines)
setDistance(lineList, point)
lineList.Sort(compare);
lineList[0].remove();
// ...
}
private static Comparison<GeomObject> compare = new Comparison<GeomObject>(GeomObject.CompareByDistance);
public static int CompareByDistance(GeomObject go1, GeomObject go2)
{
return go1.mDistance.CompareTo(go2.mDistance);
}
private void setDistance(List<Line> lineList, Point point) {
// set mDistance for each Line
// mDistance - distance to point
}

Related

Separator line drag/drop tree nodes c# WinForms

I am trying to replicate the following drag and drop functionality:
However, I am having trouble drawing that black line. I end up, somehow, with three lines per node. I calculate the upper, middle and lower part of the rectangle containing the tree node and, depending on that, i draw one of the lines. If I do use treeView.Invalidate() the screens flickers too much and the line cannot be seen. I have also tried to used graphics.clear(treeView.BackColor) but it will also clear my tree nodes.
Demo:
Code - tree events:
public void ItemDrag(object sender, ItemDragEventArgs e)
{
TreeNode selectedNode = (TreeNode)e.Item;
if (e.Button == MouseButtons.Left && !selectedNode.Name.Contains("=") && !selectedNode.Name.Contains("#"))
_treeView.DoDragDrop(selectedNode, DragDropEffects.Move);
}
public void DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.AllowedEffect;
}
public void DragOver(object sender, DragEventArgs e)
{
try
{
if (!mousePoint.Equals(Cursor.Position))
{
mousePoint = Cursor.Position;
bool droppable;
TreeNode destinationNode = null;
Point pointInTree = _treeView.PointToClient(new Point(e.X, e.Y));
if (e.Data.GetDataPresent(typeof(TreeNode)))
{
destinationNode = _treeView.GetNodeAt(pointInTree);
TreeNode souceNode = (TreeNode) e.Data.GetData(typeof(TreeNode));
droppable = true;
}
else droppable = false;
e.Effect = droppable ? DragDropEffects.Move : DragDropEffects.None;
Point pt = _treeView.PointToClient(new Point(e.X, e.Y));
_treeView.SelectedNode = _treeView.GetNodeAt(pt);
int dropLocation = CalculateNodeHooverArea(destinationNode, pointInTree);
if(_dropLocation!=dropLocation)
{
switch (dropLocation)
{
case 0:
DrawLine(NodePosition.Above);
break;
case 2:
DrawLine(NodePosition.Below);
break;
case 1:
DrawLine(NodePosition.In);
break;
}
_dropLocation = dropLocation;
}
}
}
catch (Exception exception)
{
Debug.WriteLine("TreeViewDragOverEvent: " + exception.Message);
}
}
public void DragDrop(object sender, DragEventArgs e)
{
try
{
Point targetPoint = _treeView.PointToClient(new Point(e.X, e.Y));
TreeNode targetNode = _treeView.GetNodeAt(targetPoint);
TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
if (!draggedNode.Equals(targetNode) && !draggedNode.Nodes.Find(targetNode.Name, true).Any() &&
targetNode.Parent != null && !targetNode.Name.Contains("=") && !targetNode.Name.Contains("#"))
{
int nodeLocation = CalculateNodeHooverArea(targetNode, targetPoint);
if (e.Effect == DragDropEffects.Move)
draggedNode.Remove();
switch (nodeLocation)
{
case 0:
if (targetNode.Parent != null)
targetNode.Parent.Nodes.Insert(targetNode.Index, draggedNode);
break;
case 1:
targetNode.Nodes.Add(draggedNode);
break;
case 2:
if (targetNode.Parent != null)
targetNode.Parent.Nodes.Insert(targetNode.Index + 1, draggedNode);
break;
}
}
//SaveMemento();
_treeView.Invalidate();
}
catch (Exception exception)
{
Debug.WriteLine(exception.Message);
}
}
The method that handles the drawing of the line:
private void DrawLine(NodePosition position)
{
Graphics g = _treeView.CreateGraphics();
Pen customPen = new Pen(Color.DimGray, 1) { DashStyle = DashStyle.Dash };
if (position == NodePosition.Above)
g.DrawLine(customPen, new Point(0, _treeView.SelectedNode.Bounds.Top),
new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Top));
else if (position == NodePosition.Below)
g.DrawLine(customPen, new Point(0, _treeView.SelectedNode.Bounds.Bottom),
new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Bottom));
else
{
g.DrawLine(customPen, new Point(_treeView.SelectedNode.Bounds.X + _treeView.SelectedNode.Bounds.Width,
_treeView.SelectedNode.Bounds.Y +_treeView.SelectedNode.Bounds.Height / 2),
new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Y + _treeView.SelectedNode.Bounds.Height / 2));
}
customPen.Dispose();
g.Dispose();
}
Can this be solved somehow or shall I look into a different way of displaying that sort of information? (e.g. tool tip)?
You really don't have to draw the line every time you reach near the new TreeNode. In my view, it's not a clean solution to keep using Graphics object to draw and erase something every time on drag/drop event. Instead, Here's what I call a lot more 'cleaner' way of achieving the same-
On your Windows Form, draw a line as a static control and set its visibility to false initially. Now how do you draw a line? Add a label control, add a solid or 3D border, clear the text, and set a fixed height - may be 2 pixels, and a width as needed. Place this label somewhere on bottom-left corner of the form, where it doesn't come in a way of other controls on UI.
Instead of DrawLine method, call it ShowLine or something. In this method, dynamically set the X and Y position of this new label (a line, actually) to the new location as per the position of TreeView node, and make it visible. So, every time on DragOver it would be visible on a different X and Y position, and give you the same experience as you need.
Once the item is dropped inside a node (i.e., drag-n-drop operation is complete), set the visibility of this line label to false, and also set its X and Y position back to the original one (bottom-left corner in this case).
Invalidate() is the correct method to call when you want your control to be redrawn without performing layouting algorithms.
The problem seems to be that you use a new Graphics object by calling _treeView.CreateGraphics().
You could try to just invalidate a calculated region you want to update (where the old separator lines were drawn) or alternatively use an double-buffered approach I would rather use for fully custom drawn controls but it might be worth a try: Remove OnPaintBackground() update that won't work without overriding the painting of the control in total.
As I think about that issue ... why don't you store the coords of the last line and as soon as another line gets drawn, "erase" (overpaint) it again with the same pen size and shape but with the color of the background. I know, this reads to be a bit dirty but in the end it's all about unnoticable and well performing hacks (thinking about render tricks in game engines for example). As soon as the user scrolls or does anything else that will make the overpainting unsuccessfull because the coords did change, you won't need to overpaint it either because the control as whole gets repainted by the OS.

C# keep drawed images with Graphics.DrawImage

I am trying to draw images on a panel with a click event.I managed to do that,but I want to keep the generated images.After each click,the previously generated image disapears.How can I keep all the drawed images?
This is my code untill now:
private void drawdot(object sender,PaintEventArgs e)
{
Image dot = Image.FromFile("dot.png");
var points = this.PointToClient(new Point(Cursor.Position.X-20, Cursor.Position.Y-30));
e.Graphics.DrawImage(dot, points);
}
private void grid2_Paint(object sender, EventArgs e)
{
if(started==true)
{
var points = this.PointToClient(new Point(Cursor.Position.X, Cursor.Position.Y));
coord2.Add(points.ToString());
clickuri2++;
test2_puncte.Text = "Testul 2 | Puncte: " + clickuri2;
//draw
grid2.Paint -= drawdot;
grid2.Paint += drawdot;
grid2.Invalidate();
}
}
Since you are invalidating the whole grid is redrawn, you should invalidate only the parts that you just redrawn. This is create a region and pass it to grid2.Invalidate as a parameter. Roughly it would look like something like this:
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddLines(points);
lvResults.Invalidate(new Region(path));
UPDATE:
You could also keep a list of all the clicked points and call the imagedraw method once for each new points, just add a new item to the list when clicking on the object and you should be good to go. I don't know if you could get into some race condition scenario where someone clicks while it's trying to draw on all points but it would be easily handled if that was the case

Problems with creating combined image view / camera view window using EmguCV & Windows Forms

Basically, what I'm trying to do is to create a window with list of images and large image view box. If user points at one of the images in the list, imagebox should display it, otherwise it should display live feed from a webcam.
I'm trying to achieve that by using ImageGrabbed event of EmguCV.Capture, and a field that stores index of the currently viewed image. Camera feed works alright, however, when I move the mouse over listbox, image view merely stops updating. It stays stuck at the last frame it saw, and doesn't display the image I'm pointing at. I've checked that the list has images in it, that index is correct, and that image coming from the camera is different from ones saved in the list. Also, same behavior happens when I use vanila PictureBox.
See the code (stripped of extra parts) below.
Initialization:
int ViewedFrame = -1;
var Frames = new List<Image<Rgb, byte>>();
// This list will have some images added to it along the way.
// They will be displayed in a custom drawn ListBox bound to the list.
Camera = new Emgu.CV.Capture(cameraid);
Camera.SetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_WIDTH, width);
Camera.SetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT, height);
Camera.ImageGrabbed += UpdateCamera;
Camera.Start();
Frame capture:
private void UpdateCamera(object sender, System.EventArgs ev)
{
if (ViewedFrame == -1)
Preview.Image = Camera.RetrieveBgrFrame();
else
// !!! this line has no apparent effect, image in the ImageBox is still the same
Preview.Image = Frames[ViewedFrame];
}
Mouse handling:
private void FrameList_MouseMove(object sender, MouseEventArgs e)
{
int index = FrameList.IndexFromPoint(e.Location);
if (index == ListBox.NoMatches)
ViewedFrame = -1;
else if (index != ViewedFrame)
{
ViewedFrame = index;
}
}
private void FrameList_MouseLeave(object sender, EventArgs e)
{
ViewedFrame = -1;
}
UPD: I tried adding Preview.Update() and Preview.Refresh() into the same branch as Frames[ViewedFrame], and after the conditional operator. In first case it doesn't have any effect. In second case nothing is shown at all. In case the ImageBox can't keep up updating itself, I enabled doublebuffering, but that didn't help either. Moreover, after I hover the mouse over an image in the list, the camera stream breaks too. The code above is the only one that's somewhat working.

Force pivot item to preload before it's shown

I have a Pivot with several PivotItems, one of which contains a canvas that places its items in dynamic locations (depending on the data). I get the data, and I can place the items in their place before the user can choose this item (this isn't the first pivot). However, only when I select the PivotItem, the canvas renders itself, so you can see it flicker before it's shown as it should.
Is there a way to force the canvas to render before it's shown, so everything's prepared by the time the user sees it?
My code looks something like this:
In the page.xaml.cs:
private async void GameCenterView_OnDataContextChanged(object sender, EventArgs e)
{
// Load data...
// Handle other pivots
// This is the problem pivot
if (ViewModel.CurrentGame.SportTypeId == 1)
{
_hasLineups = ViewModel.CurrentGame.HasLineups.GetValueOrDefault();
HasFieldPositions = ViewModel.CurrentGame.HasFieldPositions.GetValueOrDefault();
// I only add the pivot when I need it, otherwise, it won't be shown
if (_hasLineups)
{
if (MainPivot.Items != null) MainPivot.Items.Add(LineupPivotItem);
}
if (HasFieldPositions)
{
// Here I place all the items in their proper place on the canvas
ArrangeLineup(ViewModel.TeamOneLineup, TeamOneCanvas);
ArrangeLineup(ViewModel.TeamTwoLineup, TeamTwoCanvas);
}
}
// Handle other pivots
}
private void ArrangeLineup(ObservableCollection<PlayerInLineupViewModel> teamLineup, RationalCanvas canvas)
{
if (teamLineup == null)
return;
foreach (var player in teamLineup)
{
var control = new ContentControl
{
Content = player,
ContentTemplate = LinupPlayerInFieldDataTemplate
};
control.SetValue(RationalCanvas.RationalTopProperty, player.Player.FieldPositionLine);
control.SetValue(RationalCanvas.RationalLeftProperty, player.Player.FieldPositionSide);
canvas.Children.Add(control);
}
}
The canvas isn't the stock canvas. I created a new canvas that displays items according to their relative position (I get the positions in a scale of 0-99).
The logic happens in the OverrideArrange method:
protected override Size ArrangeOverride(Size finalSize)
{
if (finalSize.Height == 0 || finalSize.Width == 0)
{
return base.ArrangeOverride(finalSize);
}
var yRatio = finalSize.Height/100.0;
var xRatio = finalSize.Width/100.0;
foreach (var child in Children)
{
var top = (double) child.GetValue(TopProperty);
var left = (double) child.GetValue(LeftProperty);
if (top > 0 || left > 0)
continue;
var rationalTop = (int) child.GetValue(RationalTopProperty);
var rationalLeft = (int) child.GetValue(RationalLeftProperty);
if (InvertY)
rationalTop = 100 - rationalTop;
if (InvertX)
rationalLeft = 100 - rationalLeft;
child.SetValue(TopProperty, rationalTop*yRatio);
child.SetValue(LeftProperty, rationalLeft*xRatio);
}
return base.ArrangeOverride(finalSize);
}
Thanks.
There are several tricks you could try. For example:
In your ArrangeOverride you can short-circuit the logic if the size hasn't changed since last time you executed (and the data is the same)
Make sure you're listening to the events on Pivot that tell you to get ready for presentation - PivotItemLoading for example
You can have the control not actually be part of the Pivot, but instead be in the parent container (eg a Grid) and have it with Opacity of zero. Then set it to 100 when the target PivotItem comes into view.

Wpf Richtextbox selection starting column and row number returns different values

I am initializing my Richtextbox,
void InitRTBFlowDocument()
{
Style noSpaceStyle = new Style(typeof(Paragraph));
noSpaceStyle.Setters.Add(new Setter(Paragraph.MarginProperty, new Thickness(0)));
rtbTextEditor.Resources.Add(typeof(Paragraph), noSpaceStyle);
}
I want to get Richtext box selection words row and column numbers. I wrote the code as follows, First time it is returning correctly.
void rtbTextEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
//Get the richtext box selected text
Init.RTBSelectionText = rtbTextEditor.Selection.Text.Trim();
Init.SelectionText = rtbTextEditor.Selection.Text.Trim();
Init.isSelect = true;
if (Init.RTBSelectionText != string.Empty)
{
TextPointer tp = rtbTextEditor.Selection.Start.GetPositionAtOffset(-2, LogicalDirection.Forward);
if (tp != null)
GetStartingIndex();
}
Init.RTBContent = new TextRange(rtbTextEditor.Document.ContentStart, rtbTextEditor.Document.ContentEnd).Text;
}
void GetStartingIndex()
{
TextPointer tp1 = rtbTextEditor.Selection.Start.GetLineStartPosition(0);
TextPointer tp2 = rtbTextEditor.Selection.Start;
int SelectionColumnIndex = tp1.GetOffsetToPosition(tp2)-1;//column number
int someBigNumber = int.MaxValue;
int lineMoved;
rtbTextEditor.Selection.Start.GetLineStartPosition(-someBigNumber, out lineMoved); //Line number
int SelectionRowIndex = -lineMoved;
Init.RTBTextPoint = new RTBTextPointer();
Init.RTBTextPoint.Row = SelectionRowIndex;
Init.RTBTextPoint.Column = SelectionColumnIndex;
}
After clearing and added new content, The position returns wrong number,
public void DisplayContent(string content)
{
//Clear the rich text box
rtbTextEditor.Document.Blocks.Clear();
rtbTextEditor.Document.Blocks.Add(new Paragraph(new Run(content)));
}
Is anything rong in the above code.
Please help me
Thanks in advance!
This is because the contents in the RTB does not only contain text, it contains these things called TextPointerContext's. TextPointer's take this into account. You can check what the TextPointer is adjacent to by using:
TextPointer.GetPointerContext(LogicalDirection);
To get the next TextPointer:
TextPointer.GetNextContextPosition(LogicalDirection);
Some sample code I used in a recent project, this makes sure that the pointer context is of type Text:
while (start.GetPointerContext(LogicalDirection.Forward)
!= TextPointerContext.Text)
{
start = start.GetNextContextPosition(LogicalDirection.Forward);
if (start == null) return;
}
When you clear your RTB, you're probably removing some of these pointer contexts. So be careful when using GetPositionAtOffset(). This should give you a "pointer" in the right direction. If you need any more help me know.
Good hunting!

Categories

Resources