I have a method which finds relationships between two objects, if it exists, I would like to draw two Lineshapes to the link. I have started to implement the first lineshape, however whenever I test the code the lines persist. I have tried multiple methods (as you can see) and these do not refresh the canvas for the new lines to be drawn.
private void DrawRelationshipLines()
{
_canvas = new ShapeContainer {Parent = panelCredentialsVisualisation};
//These methods below do not redraw the canvas
_canvas.Shapes.Remove(_tableinfoLine);
_canvas.Shapes.Clear();
_canvas.Refresh();
_canvas.Update();
//
List<string> relationships = lvSelectedTableInfoCredentialsIntersection.GetAllRelationships();
if (relationships.Capacity == 0)
return;
foreach (string context in relationships)
{
Label contextLabelName = GetLabelByName(context);
_tableinfoLine = new LineShape
{
Parent = _canvas,
BorderWidth = 2,
BorderColor = Color.BlueViolet,
StartPoint = new Point(lblselectedTableinfo.Right, lblselectedTableinfo.Top + 10),
EndPoint = new Point(contextLabelName.Left, contextLabelName.Top + 10)
};
}
The code works fine in searching for relationships and drawing them, however I'd like to be able to clear the canvas before drawing a different relationship, is this possible?
Thanks if anyone can help.
Moving _canvas = new ShapeContainer {Parent = panelCredentialsVisualisation}; outside of the method made this work. Seems like initializing a new ShapeContainer each time was causing issues.
Related
I want to get the object that created in struct. Showing the codes will explain it better though.
private void Obstacle()
{
obstacle_pos_x = obstacle_random_x.Next(1000);
obstacle_pos_y = obstacle_random_y.Next(700);
picture = new PictureBox
{
Name = "pictureBox" + obstacle_numb,
Size = new Size(32, 32),
Location = new Point(obstacle_pos_x,obstacle_pos_y),
BackColor = Color.Black,
};
this.Controls.Add(picture);
}
This is the struct inside of Obstacle method. As you can see this method creates pictureboxes and I want to pull them into KeyPressEvents. Like, if I press W, all the pictureboxes that created by struct has to move -10(y axis).
else if (e.KeyCode == Keys.W)
{
y -= chrspeed;
obstacle_numb++;
Obstacle();
for (int i = 0; i <= obstacle_numb; i++)
{
}
}
Well this the event. But it just creates pictureboxes. For loop is empty, because I couldn't figure out what to do. I simply want to do something like that,
picture+obstacle_numb.Location = new Point(x,y); (I need this picture+obstacle_numb combination.)
But also know that it is imposible. foreach came up to my mind but I don't know how to use it. Maybe something like this can work if fixed.
foreach(PictureBox objects from picture) //It doesn't work too.
I'm stuck right now and waiting for your help. Thanks in advance.
The easiest would be to iterate all the child controls that are of type PictureBox:
...
foreach (var pict in this.Controls.OfType<PictureBox>())
{
// pict is now a Picturebox, you can access all its properties as you have constructed it
// the name was constructed that way: Name = "pictureBox" + obstacle_numb,
if (pict.Name != "pictureBox1")
{
pict.Location = new Point(pict.X, pict.Y-10);
}
}
I am trying to make a little game coded in c#, the game involves moving enemies.
These enemies are spawned in using the following code, this code is used multiple times to spawn multiple enemies.
private void EventHandler(Action<object, EventArgs> spawnBox)
{
Random randomPlek = new Random();
int xPlek;
xPlek = randomPlek.Next(1000, 1100);
int yPlek;
yPlek = (randomPlek.Next(0, 8)) * 100;
var picture = new PictureBox
{
Name = "pictureBoxLM",
Size = new Size(150, 100),
SizeMode = PictureBoxSizeMode.StretchImage,
BackColor = Color.Transparent,
Location = new Point(xPlek, yPlek),
Image = Leeuwenmier,
};
this.Controls.Add(picture);
}
The problem is that when trying to make them move or collide, Visual Studio can't find the name and gives an error. This is the code i used for collision:
if(PbMier.Bounds.IntersectsWith(pictureBoxLM.Bounds))
{
// some actions
}
How can I call the spawned picturebox in the code without getting an error?
WinForms controls have names, but that doesn't mean you can access them using that name as a C# identifier.
Your PictureBox only has a named reference within EventHandler(), namely picture, but once control leaves that method that reference goes out of scope.
You need to find the controls again, or find another way to reference the generated controls.
So either:
var allPictureBoxes = this.Controls.Find("PictureBoxLM");
foreach (var pictureBox in allPictureBoxes)
{
// ...
}
Or put this on your form:
List<PictureBox> pictureBoxList = new List<PictureBox>();
And then in the EventHandler();
this.Controls.Add(picture);
pictureBoxList.Add(picture);
After which you can use this for your collision detection:
foreach (var pictureBox in pictureBoxList)
{
// ...
}
I am working with a WPF control derived from the Canvas that I am drawing a selection of geometry to, through a process that works along these lines, but is far more complex so although this illustrates the process, it is closer to pseudo-code:
public class LineView: Canvas
{
private List<GeometryLine> lines;
public LineView()
{
lines = new List<GeometryLine>();
}
private PathFigure GetPathFigure(List<Point> line, bool close)
{
var size = line.Count;
var points = new PathSegmentCollection(size);
var first = line.First();
for (int i = 1; i < size; i++)
{
points.Add(new LineSegment(line[i], true));
}
var figure = new PathFigure(first, points, close);
return new PathGeometry(new List<PathFigure>(figure));
}
public DrawingGroup DrawLine(GeometryLine line)
{
var geometry = new GeometryGroup();
geometry.Children.Add(GetPathGeometry(line.Points, false));
var d = new GeometryDrawing();
d.Pen = GetPen();
d.Geometry = geometry;
DrawingGroup group = new DrawingGroup();
group.Append();
group.Children.Add(d);
return group;
}
public override void OnRender(DrawingContext drw)
{
if ( 0 < lines.Count )
{
foreach ( var line in lines )
{
drw.DrawDrawing(DrawLine(line));
}
}
}
public void SetLines(List<GeometryLine> newLines)
{
lines = newLines;
this.InvalidateVisual();
}
}
This all works fine to render geometry content on the canvas. However, when the data changes and SetLines is called with a new set of data, it doesn't always clear the canvas - sometimes it will draw the new set of lines over the old set, other times it will clear the canvas. I can't see any pattern as to when it draws over as opposed to when it clears the canvas.
If I call InvalidateVisual from the Render method it will empty the canvas reliably, but it also forces the context to render again. Past questions on this topic indicate that either this.Children.Clear() or InvalidateVisual are the recommended strategies, but neither of them prevents this overdrawing problem. When I look at the Children collection it is always empty.
What do I need to do in order to ensure it will clear the previous geometry and then draw the updated geometry every time it changes?
I am currently following this tutorial for adding a polygon to a map. I need to be able to add multiple polygons to my map, so I have slightly altered the code so that I can use addOverlays which takes in an array of IMKOverlay objects instead of one addOverlay which just takes in a single IMKOverlay object.
This doesn't work however... It only draws the first polygon on the map!
void addPolygonsToMap()
{
overlayList = new List<IMKOverlay>();
for (int i = 0; i < polygons.Count; i++)
{
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[polygons[i].Count];
int index=0;
foreach (var position in polygons[i])
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var blockOverlay = MKPolygon.FromCoordinates(coords);
overlayList.Add(blockOverlay);
}
IMKOverlay[] imko = overlayList.ToArray();
nativeMap.AddOverlays(imko);
}
In this discussion, it would appear that I have to call a new instance of MKPolygonRenderer each time I need to add another polygon to my map, but I'm unsure how this example translates to my code. Here is my MKPolygonRenderer function:
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (polygonRenderer == null && !Equals(overlayWrapper, null)) {
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
polygonRenderer = new MKPolygonRenderer(overlay as MKPolygon) {
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
}
return polygonRenderer;
}
Create a new renderer instance each time OverlayRenderer is called, there is no need to cache the renderer in a class level variable as the MKMapView will cache the renderers as needed.
Subclass MKMapViewDelegate:
class MyMapDelegate : MKMapViewDelegate
{
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
switch (overlay)
{
case MKPolygon polygon:
var prenderer = new MKPolygonRenderer(polygon)
{
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
return prenderer;
default:
throw new Exception($"Not supported: {overlay.GetType()}");
}
}
}
Instance and assign the delegate to your map:
mapDelegate = new MyMapDelegate();
map.Delegate = mapDelegate;
Note: Store the instance of your MyMapDelegate in a class level variable as you do not want to get GC'd
Update:
MKMapView has two steps involved to display an overlay on its map.
1. Calling `AddOverlay` and `AddOverlays`
First you add overlays to the map that conform to IMKOverlay. There are basic built-in types such as MKCircle, MKPolygon, etc... but you can also design your own overlays; i.e. overlays that define the location of severe weather (lightning, storm clouds, tornados, etc..). These MKOverlays describe the geo-location of the item but not how to draw it.
2. Responding to `OverlayRenderer` requests
When the display area of the map intersects with one of the overlays, the map need to draw it on the screen. The map's delegate (your MKMapViewDelegate subclass) is called to supply a MKOverlayRenderer that defines the drawing routines to paint the overlay on the map.
This drawing involves converting the geo-coordinates of the overlay to local display coordinates (helper methods are available) using Core Graphics routines (UIKit can be used with some limitations). There are basic built-in renderers for MKCircleRenderer, MKPolygonRenderer, etc.. that can be used or you can write your own MKOverlayRenderer subclass.
You could supply a custom way to renderer a MKCircle overlay, maybe a target-style red/white multi-ringed bullseye, instead of the way the default circle renderer draws it, or custom renderers that draw severe storm symbols within the bounds of a MKPolygon to match your custom severe storm overlays.
My Example code:
Since you are using MKPolygon to build your overlays, you can use the MKPolygonRenderer to display them. In my example, I provide a pattern matching switch (C# 6) that returns a semi-transparent Red/Blue MKPolygonRenderer for every MKPolygon that you added to the map (if you added a non-MKPolygon based overlay it will throw an exception).
I was also stuck in this issue and I have found the way to create the sub class of MKPolygon.
I have checked it with my example and it works like a charm. But not sure that Apple may reject my app or not.
public class CvPolyon : MKPolygon
{
public CustomObject BoundaryOption { get; }
public CvPolyon1(MKPolygon polygon, CustomObject boundaryOption)
:base(polygon.Handle)
{
BoundaryOption = boundaryOption;
}
}
We can add polygon on map like this.
var polygon = MKPolygon.FromCoordinates(coordinates);
var overlay = new CvPolyon(polygon, new CustomObject());
mapView.AddOverlay(overlay);
We can recognize our polygon in the class which extends MKMapViewDelegate like this.
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
if (overlay is CvPolyon polygon)
{
var polygonRenderer = new MKPolygonRenderer(polygon)
{
FillColor = polygon.BoundaryOption.AreaColor,
StrokeColor = polygon.BoundaryOption.LineColor,
Alpha = polygon.BoundaryOption.Alpha,
LineWidth = polygon.BoundaryOption.LineWidth
};
if (polygon.BoundaryOption.IsDashedLine)
polygonRenderer.LineDashPattern = new[] { new NSNumber(2), new NSNumber(5) };
return polygonRenderer;
}
return mapView.RendererForOverlay(overlay);
}
Here is my code
public GeoCoordinateCollection AddCirclePath(GeoCoordinate geoCoordinate, Color stroke, double strokeThickness)
{
Point geoPoint = map.ConvertGeoCoordinateToViewportPoint(geoCoordinate);
MapPolygon polyCircle = new MapPolygon();
polyCircle.FillColor = Color.FromArgb(250, 220, 220, 0);
polyCircle.StrokeColor = stroke;
polyCircle.StrokeThickness = strokeThickness;
polyCircle.StrokeDashed = false;
polyCircle.Path = MapUtils.CreateCircle(geoCoordinate, geoCoordinate.HorizontalAccuracy);
map.MapElements.Add(polyCircle);
//click event
GestureListener gestureListener = GestureService.GetGestureListener(polyCircle);
gestureListener.Tap += new EventHandler<GestureEventArgs>(CircleTapped);
return polyCircle.Path;
}
private void CircleTapped(object sender, GestureEventArgs e)
{
Logger.Log("Circle tapped");
}
I add circle on map using above code but I'm unable to get tap event on this map element. I'm using Microsoft.Phone.Maps.Controls.Map class for map
There's several bugs in the control and hit testing. One solution is sometimes to add the last vextex in your element twice (I know it doesn't make sense but I've seen this resolve tap issues with polylines, so I'm guessing it's the same with polygon). It could also be that your ring orientation is wrong (clockwise vs counterclockwise)
(This is quite old post but in case someone else is looking for solution, here's a simple example.)
You should be able to use API directly to get the tapped map element w/o any conversions or workarounds.
Add a polygon.
var collection = new GeoCoordinateCollection();
collection.Add(new GeoCoordinate(0d, 0d));
collection.Add(new GeoCoordinate(10d, 0d));
collection.Add(new GeoCoordinate(10d, 10d));
collection.Add(new GeoCoordinate(0d, 10d));
collection.Add(new GeoCoordinate(0d, 0d));
var poly = new MapPolygon();
poly.FillColor = Color.FromArgb(80, 255, 0, 0);
poly.StrokeColor = Colors.Red;
poly.StrokeThickness = 15;
poly.Path = collection;
Map.MapElements.Add(poly);
And catch the tap.
private void Map_OnTap(object sender, GestureEventArgs e)
{
var point = e.GetPosition(Map);
//var coordinate = Map.ConvertViewportPointToGeoCoordinate(point);
int elements = Map.GetMapElementsAt(point).Count;
System.Diagnostics.Debug.WriteLine(string.Format("Hit {0} map element(s)", elements));
}
Bug regarding polylines does not apply to polygons, you do not need to add the last point twice. You should still have same starting- and ending points to keep the polygon closed (although this is not mandatory, either). Ring orientation does not seem to affect wether element is selectable or not.