Check to avoid adding multiple items to canvas in WPF/C# - c#

Shape shape = sm.maakEllips();
if (!canvas.Children.Contains(shape))
{
cm.Draw(shape, canvas, locatie);
}
public void Draw(Shape vorm, Canvas canvas, Point locatie)
{
if (vorm.Height <= canvas.Height && vorm.Width <= canvas.Width)
{
Canvas.SetTop(vorm, locatie.Y);
Canvas.SetLeft(vorm, locatie.X);
canvas.Children.Add(vorm);
}
}
So I add a shape to a canvas in the Draw(). Then when I check on this in the upper if clause, I'm still able to add the same shape to the same canvas multiple times.
I don't get it, what am I doing wrong?
EDIT:
Shape shape = sm.makeShape(Convert.ToByte(textboxR.Text), Convert.ToByte(textboxG.Text), Convert.ToByte(textboxB.Text), Convert.ToInt32(textboxHoogte.Text), Convert.ToInt32(textboxBreedte.Text));
foreach (Shape existingShape in canvas.Children.OfType<Shape>())
{
if (existingShape.Width != shape.Width && existingShape.Height != shape.Height
&& existingShape.Fill != shape.Fill)
{
cm.Draw(shape, canvas, locatie);
}
}
I tried this and now I'm not even able to add a shape to the canvas at all.
I don't see what I'm doing wrong at all.

Your Draw() method add vorm of type Shape to the canvas specified in canvas. And I assume your sm.maakEllips() returns an ellipse.
Therefore, when you run the following code:
Shape shape = sm.maakEllips();
if (!canvas.Children.Contains(shape))
{
cm.Draw(shape, canvas, locatie);
}
You will go inside the if statement only if the canvas contains that exact shape object you created in the line above, using sm.maakEllips() method. It cannot be any shape that has the same properties of the shape object above. Because, every time you create a new object, even with the exact same properties including its name, they are still two distinct objects in .NET world.
To illustrate the point see the code sample below.
Your unchanged Draw() method:
public void Draw(Shape vorm, Canvas canvas, Point locatie)
{
if (vorm.Height <= canvas.Height && vorm.Width <= canvas.Width)
{
Canvas.SetTop(vorm, locatie.Y);
Canvas.SetLeft(vorm, locatie.X);
canvas.Children.Add(vorm);
}
}
A makeEllipse() method that creates an ellipse of width and height of 100 and 80 respectively, and assigns the name passed in the parameter.
public Shape makeEllipse(string name)
{
Shape sh = new Ellipse
{
Name = name,
Width = 100,
Height = 80,
};
return sh;
}
Now see the following code, executed at the click of a button.
private void btnGO_Click(object sender, RoutedEventArgs e)
{
// Creates an ellipse with name "Shape1", and assigns to sh1.
Shape sh1 = makeEllipse("Shape1");
// Adds the said sh1 to the canvas using `Draw()` method.
Draw(sh1, myCanvas, new Point(5, 5));
// See if sh1 exists as a child of `myCanvas`.
// Since sh1 is now a child of canvas, code does NOT go inside the if-clause.
if (!myCanvas.Children.Contains(sh1))
{
Draw(sh1, myCanvas, new Point(5, 5));
}
// Creates an ellipse with the same name "Shape1", and assigns to sh2.
Shape sh2 = makeEllipse("Shape1");
// It is NOT added to the canvas using `Draw()` method.
// Now, here, code DOES go inside the if-clause, because the said object does not exist as a child of `myCanvas`.
if (!myCanvas.Children.Contains(sh2))
{
Draw(sh2, myCanvas, new Point(5, 5));
}
}
Comments above should be good enough, but to explain again,
When you create sh1 and adds it to myCanvas using Draw() method, it becomes a child element of myCanvas.Children.
Then, when you check if it is a child using if (!myCanvas.Children.Contains(sh1)), since it IS a child element by that time, condition becomes false and we do not go inside the if clause.
Next, we create sh2, which has the exact same dimensions and the name as sh1. However, and this is the key, .NET treats it as a different object even though it has the same properties as the previous object. Reason being, whenever we use the new keyword, .NET creates an actual new object.
Afterwards, we DON'T add it to the canvas using Draw() method.
Now, at the second if when we check if myCanvas contains the object, it finds that sh2 is NOT a child of myCanvas, so it goes inside the if clause.

maakEllips() always creates a new Shape. If you want to compare this one against other Shape elements in the Canvas, you need to iterate through these. The following code compares the height, width and position and adds the new Shape to the Canvas if any of them differ:
Shape shape = sm.maakEllips();
foreach (Shape existingShape in canvas.Children.OfType<Shape>())
{
if(existingShape.Width != canvas.Width || existingShape.Height != canvas.Height
|| Canvas.GetLeft(existingShape) != Canvas.GetLeft(shape)
|| Canvas.GetTop(existingShape) != Canvas.GetTop(shape))
{
cm.Draw(shape, canvas, locatie);
}
}

Related

Move ROI rectangle by dragging the rectangle by mouse. Dragging works, but I need the coordinates from the dragged rectangle for moving the ROI

I am still a rookie concerning C# and wpf. I added a “DragCanvas” so that I can drag every element inside that canvas. This works for example with a rectangle element I create statically inside the .xaml-file.
How can I reference to that rectangle, which was created inside the .xaml-file MainWindow.xaml.cs-file? I have to insert the width and height of the statically created rectangle, which was created inside the .xaml-file, inside my c#-code (inside my MainWindow.xaml.cs) and give it as arguments of the repositionROI function.
The topic of my project is to drag and position a rectangle which selects a ROI from a bitmap. At the moment, 5 rectangles are created as one clicks of the bitmap during runtime. The following code dynamically creates 5 rectangles, which selects 5 ROIs inside a bitmap:
private void drawROISelection()
{
this.manualROIselection = new Bitmap(this.originalScan);
Graphics g = Graphics.FromImage(this.manualROIselection);
Pen referencePen = new Pen(Color.DarkGreen, 10);
g.DrawRectangle(referencePen, new Rectangle(referenceROI.x0, referenceROI.y0, referenceROI.width, referenceROI.height));
Pen bluePen = new Pen(Color.Blue, 10);
foreach (ROI roi in this.ROIs)
{
g.DrawRectangle(bluePen, new Rectangle(roi.x0, roi.y0, roi.width, roi.height));
}
bluePen.Dispose();
g.Dispose();
}
And then there is the code, which moves the rectangles respectively after new coordinates were typed in the MoveAllROI-Window.
That would be the following code:
private void MoveROI_Clicked(object sender, RoutedEventArgs e)
{
if (ScanList == null) return;
if (this.selectedIndex < 0) return;
Scan selectedScan = scans[this.selectedIndex];
int roiNumber = (int)ROITitleLabel.Tag;
MoveROIWindow move = new MoveROIWindow(selectedScan.ROIs[roiNumber]);
move.ShowDialog();
if (move.DialogResult == true)
{
selectedScan.repositionROI(roiNumber, move.x0, move.y0, move.width, move.height);
PreviewStyle_Changed(null, null);
}
}
The moving of the ROI-rectangles must be done by dragging them in the new version of the program (dragging by mouse). There is a mouse-up event in the DragCanvas.cs-file. I need the coordinates of the dragged rectangle if the MouseButton is realead, i.e. end of the dragging of the rectangle. I guess I have to write a function, which gets the coordinates inside the following function inside the DragCanvas.cs-file?:
protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseUp(e);
// Reset the field whether the left or right mouse button was
// released, in case a context menu was opened on the drag element.
this.ElementBeingDragged = null;
}
Or is it better to solve it with the “INotifyPropertyChanged.PropertyChanged Event”. With the latter I never done anything yet. So, I would have to read about it first. I am really stuck on this dragging ROI_rectangle problem and at first one thinks it could not be that difficult. I need a hint. Thanks in advance.
Edit: In my first post I also asked if I could reference my dynamically drawn rectangles inside the .xaml-file. That is of course not possible and does not make sense, because everything which is declared inside the .xaml-file are static elements and the dynamically drawn rectangles are only drawn during runtime of the program. Therefore this question only aims to find out how I reference to a rectangle which was statically created inside the .xaml file. My MainWindow.xaml.cs does not recognzize the rectangle name as a variable name inside the cs-code. I created the rectangle inside the .xaml file as follows:
<Rectangle x:Name="ROI_rectangle1" Width="200" Height="25" Fill="Blue" Opacity="0.5" Stroke="Blue" StrokeThickness="3" Canvas.Left="135" Canvas.Top="213"/>
and created it inside the DragCanvas because the rectangle has to be draggable during runtime.

Stop System.Windows.Shapes.Rectangle inertia if collision with another shape happens in WPF touchscreen app

I am working on touchscreen app. This app is for a certain industry, but the general ideals are all that is needed to explain issue. To start, im doing a crash course on WPF and am allowing a user to add a new command (Shape - Rectangle) then will assign functions to it. Example, they can add one, then add second, then a third and so on positioning them where they want.
What i would like to find out is how to tell the API when inertia is occurring and say a user touches a shape then flings it across screen, for the event to not only complete when it reaches the edge of the screen (already does this), but also if it intersects or hits another shape. Below is the general code for a quick basic proof of concept.
void Window_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
try
{
// Get the Rectangle and its RenderTransform matrix.
Rectangle rectToMove = e.OriginalSource as Rectangle;
Matrix rectsMatrix = ((MatrixTransform)rectToMove.RenderTransform).Matrix;
// Rotate the Rectangle.
rectsMatrix.RotateAt(e.DeltaManipulation.Rotation,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
// Resize the Rectangle. Keep it square
// so use only the X value of Scale.
rectsMatrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.X,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
// Move the Rectangle.
rectsMatrix.Translate(e.DeltaManipulation.Translation.X,
e.DeltaManipulation.Translation.Y);
// Apply the changes to the Rectangle.
rectToMove.RenderTransform = new MatrixTransform(rectsMatrix);
Rect containingRect =
new Rect(((FrameworkElement)e.ManipulationContainer).RenderSize);
Rect shapeBounds =
rectToMove.RenderTransform.TransformBounds(
new Rect(rectToMove.RenderSize));
// Check if the rectangle is completely in the window.
// If it is not and intertia is occuring, stop the manipulation.
if (e.IsInertial && !containingRect.Contains(shapeBounds))
{
e.Complete();
}
if (e.IsInertial)
{
//convert current moving rect to new rect drawing type
var tempRect = new Rect(Canvas.GetLeft(rectToMove), Canvas.GetTop(rectToMove), rectToMove.Width, rectToMove.Height);
//we need to convert SytsemWindows.Shapes.Rectablge to SystemWindows.Drawing.Rect first
//List<System.Windows.Shapes.Rectangle> removeCurrent = Commands.Where(r => r != rectToMove).ToList();
//List<System.Windows.Rect> coordinates = Commands.Where(r => r.Name != rectToMove.Name).Select(r => new Rect(Canvas.GetLeft(r), Canvas.GetTop(r), r.Width, r.Height)).ToList();
List<System.Windows.Rect> recs = new List<Rect>();
List<Rectangle> test = Commands.Where(r => r.Name != rectToMove.Name).ToList();
foreach (System.Windows.Shapes.Rectangle re in test)
{
if (re != rectToMove)
{
recs.Add(new Rect(Canvas.GetLeft(re), Canvas.GetTop(re), re.Width, re.Height));
}
}
//if rect item is in motion inertia and it intersects with any of pre made command rects, then stop.
if (recs.Any(c => c.InteriorIntersectsWith(tempRect)))
{
//There is overlapping
e.Complete();
}
}
}
catch(Exception ex)
{
}
e.Handled = true;
}
The problem is inertia works fine. Once a second shape is then added to the canvas, inertia no longer works. The user can click and drag them but when trying to flick the shapes they stop instantly as if it reports from all positions that it is intersecting with another shape even when they are no where near each other. Any ideas on what i am doing wrong?

Create combined Region for UserControl

I want my UserControl to automatically update its Region property. I want it to be a combination of child controls' regions merged together.
Here what I have so far:
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Region region = new Region(new Rectangle(Point.Empty, Size.Empty));
foreach (Control control in Controls)
{
if (control.Region != null)
region.Union(control.Region);
else
region.Union(control.Bounds);
}
Region = region;
Invalidate();
}
Problem is that it does not work: Line region.Union(control.Region); must be changed because Region does not include information about left and top offset of the control.
What can I do?
You have a choice of either going for the Rectangles that actually make up a Region. You can get them via GetRegionScans. You can see them in this post.
Or of using the GraphicsPaths your child controls' Regions originate from..
In both methods you can move the controls' region data by its location: Either by offsetting each rectangle or by translating the whole graphicspath.
Here is a code example for the 1st method:
if (control.Region != null)
{
Matrix matrix = new Matrix(); // default, unscaled screen-resolution matrix
var rex = control.Region.GetRegionScans(matrix); // get rectangles
foreach (var r in rex) // use each of them
{
r.Offset(control.Location); // move by the location offsets
region.Union(r);
}
else
{
region.Union(control.Bounds);
}
The problem is that this tends to get slower and slower with the 'vertical' size and the complexity of the Region shapes..
The other way is to keep track of the GraphicsPaths of the child controls.
Assuming a class PathControl with a control property
public GraphicsPath path { get; set; }
You could change the loop maybe to this:
foreach (Control control in Controls)
{
if (control is PathControl)
{
// use a clone, so the original path won't be changed!
GraphicsPath gp = (GraphicsPath)(control as PathControl).path.Clone();
Matrix matrix = new Matrix();
matrix.Translate(control.Left, control.Top);
gp.Transform(matrix); // here we move by the location offsets
region.Union(gp);
else
{
region.Union(control.Bounds);
}
}

Drawing multiple rectangles chokes my WPF app

I am working in my own editor for making WPF forms. My issue is that I am having the worst time selecting multiple controls (buttons, labels, etc) and dragging them smoothly across the main window. My application chokes when I try to drag, oh say, 20 selected buttons at the same time.
I found that the culprit is the fact that I am drawing multiple rectangles for each object as they are being dragged and this function is being called in the MouseMove event.
void ControlObjectControl_MouseMove(object sender, MouseEventArgs e)
{
if (newPosition != oldPosition)
{
DragSelection(newPosition);
}
}
private void DragSelection(Point newPosition)
{
foreach (FrameworkElement item in designer.SelectionService.CurrentSelection)
{
if (item is ObjectControl)
(item as ObjectControl).m_ParentControlObject.Position = new System.Drawing.Rectangle() { X = X, Y = Y, Width = (int)item.Width, Height = (int)item.Height };
//..There's code that calculates the item's position and dimensions
}
}
How do I make it to where it only draws the rectangle once and I am still able to see my selected object(s) move smoothly when I drag them?
I did something similar in my application except I used a TranslateTransform to move my elements. Each "frame" (every mouse move) that I was dragging, I got the position of the mouse and compared that to the previous position of the mouse. I would then set a new TranslateTransform X/Y values equal to the X/Y mouse position change and then would give that to the RenderTransform of each object I wanted to move. Something like:
void ControlObjectControl_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
// Get the change in Location
mouseLocation = Mouse.GetPosition();
Point deltaLocation = mouseLocation - previousLocation;
// Make a new transform
TranslateTransform transform = new TranslateTransform();
transform.X = deltaLocation.X;
transform.Y = deltaLocation.Y;
// Apply the transform
// foreach thing
thing.RenderTransform = transform;
// set previous location
previousLocation = mouseLocation;
}
}
Now your objects only get drawn once and only their positions get changed. Hope this helps

Moving rectangles one by one (using class and foreach loop)

I have write codes to move more than one rectangles posted
here.
moving rectangles one by one
However, in order to make my code neat, I rewrote it in such a way below using class and foreach loop. But this time, I can't move them one by one. Any idea to fix it please?
Many thanks in advance.
In my object.cs class:
public bool GetSelected(int x, int y)
// public Rectangle GetSelected(List<Rectangle> squares)
{
bool GetSelected = false;
MouseState mouse = Mouse.GetState();
// Rectangle mousePosition = new Rectangle(mouse.X, mouse.Y, 200, 80);
if (drawRectangle.Contains(mouse.X, mouse.Y) && (mouse.LeftButton == ButtonState.Pressed))
{
GetSelected = true;
}
return GetSelected;
}
In my code in main.cs, I used the following to move rectangle. However, If I have more than one rectangles, how can I select one rectangle each time if some rectangles are overlap?
Thanks.
foreach (Chemtile chemtile in tiles)
{
if (chemtile.GetSelected(mouse.X, mouse.Y))
{
chemtile.drawRectangle.X = mouse.X - BoxWidth / 2;
chemtile.drawRectangle.Y = mouse.Y - BoxHeight / 2;
}
}
You need to do something call Hit-Testing. Basically loop through your rectangles from the "top" of the screen to the bottom. The first rectangle that contains the click bounds is the rectangle that was clicked on. Once you find the first rectangle you should stop searching.
The key to making this work is that the rectangles need to be tested in the top down order that they appear visually on the screen and that you return only the first rectangle that contains the point.

Categories

Resources