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);
}
}
Related
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?
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);
}
}
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
I'm busy with a small application in which I want to display information at the location of the cursor when it hoovers over a Canvas. The Canvas in question is a custom one (inherited from Canvas) which provides functionality to add DrawingVisuals (as shown in basically every tutorial on displaying large amounts of geometric shapes on a canvas).
I would like to display a vertical line and horizontal line as well as the local coordinates (p in the code below) which are directly derived from the canvas coordinates (v). At the moment I'm rendering these objects at position (0,0) and use offset during the OnMouseMove event to update their location.
The horizontal and vertical lines are rendered in the DrawingVisual _cursor and the location in local y,z-coordinates in _info.
private void oCanvas_MouseMove(object sender, MouseEventArgs e)
{
#region 1. Get location data
System.Windows.Vector v = (System.Windows.Vector)e.GetPosition(oCanvas);
// point in YZ coordinates
BSMath.DoubleXY p = new BSMath.DoubleXY();
p.X = (oCanvas.OriginY - v.Y) / oCanvas.ZoomFactor;
p.Y = (oCanvas.OriginX - v.X) / oCanvas.ZoomFactor;
#endregion
#region 2. Update cursor and info
if (oSettings.ShowInformation)
{
_info.Info = p.X.ToString("0.0") + " | " + p.Y.ToString("0.0");
_info.Render(0, 0);
_info.Visual.Offset = v;
}
// move cursor
_cursor.Visual.Offset = v;
}
Using the mousemove event seems to be creating a lot of overhead and I can see that there are issues tracking the mouse movements when I move the mouse quickly.
Can anyone recommend a better way of creating the same effect?
example http://www.iccg.be/test/images/canvas.jpg
Edit:
I investigated it a bit further and the problem seems to occur when the resolution of the canvas is bigger. If it is a 600x400 canvas then there is no delay, but when it is around 1000x800 I get the problem with delays when hoovering. The performance also improves if I use user drawn crosshairs instead of the lines that have the full width/ height of the canvas.
I recently have built something similar and haven't had any performance issues.
Did it the very simple way by adding the stuff directly on the canvas.
The drawn items are in a second canvas behind the mouse position canvas. Both reside in a Grid.
This is for sure not the most sophisticated way to solve this, but it works quite well for me.
Here's the code:
private Point _previous;
private Point _current;
private Line _xLine;
private Line _yLine;
private TextBlock _displayTextBlock;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
_current = e.GetPosition(myCanvas);
if (_previous != _current)
{
if (_xLine == null)
{
_xLine = new Line() {X1 = 0, X2 = myCanvas.ActualWidth, Stroke = new SolidColorBrush(Colors.Black)};
_yLine = new Line() {Y1 = 0, Y2 = myCanvas.ActualHeight, Stroke = new SolidColorBrush(Colors.Black)};
_displayTextBlock = new TextBlock();
myCanvas.Children.Add(_xLine);
myCanvas.Children.Add(_yLine);
myCanvas.Children.Add(_displayTextBlock);
}
_displayTextBlock.SetValue(Canvas.TopProperty, _current.Y);
_displayTextBlock.SetValue(Canvas.LeftProperty, _current.X);
_displayTextBlock.Text = _current.X.ToString() + " | " + _current.Y.ToString();
_xLine.Y1 = _current.Y;
_xLine.Y2 = _current.Y;
_yLine.X1 = _current.X;
_yLine.X2 = _current.X;
_previous = _current;
}
}
I have an imagelist of about 30 images, and 3 images I'd like to be able to overlay on top of the 30 when a TreeNode is in a particular state. I know that a C++ TreeItem can do this with the TVIS_OVERLAYMASK as such:
SetItemState(hItem,INDEXTOOVERLAYMASK(nOverlayIndex), TVIS_OVERLAYMASK);
Is there any mechanism to achieve similar results in .NET?
I see this question is still getting views, so I'll post the implementation of what David suggested.
internal class MyTree : TreeView
{
internal MyTree() :
base()
{
// let the tree know that we're going to be doing some owner drawing
this.DrawMode = TreeViewDrawMode.OwnerDrawText;
this.DrawNode += new DrawTreeNodeEventHandler(MyTree_DrawNode);
}
void MyTree_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
// Do your own logic to determine what overlay image you want to use
Image overlayImage = GetOverlayImage();
// you have to move the X value left a bit,
// otherwise it will draw over your node text
// I'm also adjusting to move the overlay down a bit
e.Graphics.DrawImage(overlayImage,
e.Node.Bounds.X - 15, e.Node.Bounds.Y + 4);
// We're done! Draw the rest of the node normally
e.DefaultDraw = true
}
}
Why don't you just generate the image with the overlay on demand even, so you don't have to waste precious CPU cycles like this:
http://madprops.org/blog/highlighting-treenodes-with-an-overlay-image/ :
private void InitializeLinkedTreeImages()
{
foreach (string key in treeImages.Images.Keys)
{
Bitmap bmp = new Bitmap(treeImages.Images[key]);
Graphics g = Graphics.FromImage(bmp);
g.DrawImageUnscaled(Properties.Resources.Linked16, 0, 0);
treeImages.Images.Add(key + "Linked", bmp);
}
}
I don't know of a way to do the overlay automatically, but you could do this with an owner drawn tree node.