I have a c# WinForms project with a picture box that contains a document with text. I am gathering the OCR data for the document using the Google Cloud Vision API, which works great. Using the bounding rectangles returned from the Google API, I am drawing rectangles around each word using DrawRectangle, and in the process I am associating that rectangle with the underlying word. What do I need to do to be able to just click on any given rectangle and know exactly which rectangle it is without having to take the point clicked and loop through all the coordinates of all the rectangles until I find it.
Four options for ya OP
Just loop
Take the point clicked and loop through all the coordinates of all the rectangles until I find it
This is actually the simplest answer and possibly the best performing answer for a relatively small (<1000) rectangles. If your rectangles might overlap, be sure to store and loop through them in z-order from front to back.
Assisted lookup
If you have a crap ton of rectangles, you could create an additional data structure to assist with lookups. For example, you could define a 10x10 array where each element contains a list of the rectangles that overlap a portion of the screen. That way you can narrow the search. Of course there is additional overhead of maintaining the list, so it may not be worth it, depending on your usage characteristics.
Custom controls
As an alternative, you could change your approach completely and render each rectangle as its own custom control. As a custom control, it will have a click event handler just like any Win32 window. However there is considerable overhead in instantiating and managing all those controls, so this is not recommended for a large number of rectangles. Also, under the covers I'm pretty sure it'll end up using the same lookup algorithms described above, so it won't perform any better.
Bindable class
A final option is to create a class specifically for the rectangle and "bind" it to the PictureBox (register as a consumer for its events). Then every rectangle will handle the click event and raise its own event if the click was within its boundaries. Here is an example to get you started:
class ClickableRectangle
{
private Rectangle _box;
public event EventHandler Click;
public ClickableRectangle(Rectangle coordinates)
{
_box = coordinates;
}
public void BindToControl(Control control)
{
control.MouseUp += Control_MouseUp;
control.Paint += Control_Paint;
}
private void Control_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Black, _box);
}
private void Control_MouseUp(object sender, MouseEventArgs e)
{
if (!_box.Contains(e.X, e.Y)) return;
if (Click != null) Click(this, e);
}
}
Then to display a new rectange in MyPictureBox, and to handle them with a method called MyClickHandler, just call
var r = new ClickableRectangle(myRectangle);
r.BindToControl(MyPictureBox);
r.Click += this.MyClickHandler;
Voila.
See also this related question.
Related
I want to write a block diagram design tool (something similar to Simulink or Modelica). I have done something like that already in C++/Qt and C#/WinForms/GDI+, but now I want to go WPF.
Take a block, which can be a rectangle or a square that contains several other smaller shapes as "input/output/bidirectional ports" probably labelled or not, some text and probably a bitmap or vector image. It should provide context menus, basic mouse or drag events (for moving the block, pulling the connections between blocks, etc.) and should allow manual rearrangement of its graphical constituents, maybe in a different editor.
Now imagine a diagram with say 1000 of such blocks (I am exaggerating a bit to allow enough headroom in the future) and corresponding connections. Given that scale, I wonder if I should fall back on to the visual level of WPF and model the interaction part manually or if it is sufficient to use Drawings, Shapes or even Controls for it (like a block being a button).
I am getting a little nervous when I see the ~50 event types a Button supplies and multiply this with the number of blocks times the average number of ports per block. Many elements will just point to the same event handlers or context menus, so these handlers could also be redirected to a management class.
I read through the respective WPF chapters in the book "Pro C# 5.0" and that actually did not allay my fears.
So what level of WPF (visual, drawing, shape, control) is advisable when it comes to speed and memory performance under these requirements?
Sidenote: I am just starting with WPF, so I am a bit stunned about its versatility. It makes strategic decisions a bit difficult for me, which is why I am asking before comprehensively researching.
You could try to create a custom layout with virtualization or use an existing one VirtualizationCanvas.
You need a custom panel for placing your scheme items at correct places in your scheme.
Also, you should create a custom control, based on ItemsControl with custom ItemsControlItems for handling items creating and so on.
Then apply your layout as ItemPanleTemplate for ItemsControl or ListView, where ItemsSource would be bounded to your viewModel with scheme collection.
That's the easiest option, as for me.
So now I have made a small feasibility study on the topic. Add 1800 Buttons to a Canvas, add two mouse events to each of them individually and further add a MouseWheel event to the window to allow basic zooming of the "scene". Observations are on my 10-year old Intel Core2Quad Q6600 + NVidia GTX 460 based machine.
Application starts up fast. Exe is very small (as expected), 8k. Resizing the window (to see more or less of the docked canvas' contents) feels quite snappy. However near fullscreen (all Buttons visible) redraw becomes a little heavy (like ~5Hz refresh rate). App grabs itself 20M of memory instead of 10M with only 1 Button. Zooming is fast enough. Moreover, when zoomed in, the rendering gets noticeably faster (so clipping and such works well). This is good because being in the closely zoomed-in state is the most frequent case when working with larger diagrams. Response to button events is unimpaired in every respect.
Conclusion: using Buttons for diagramming seems doable. It is certainly suboptimal as a graphics app gauged against the capabilities of my machine, but it is still fast enough for the job. Maybe using Shapes instead of Buttons gets a little more out of it. But definitely no case for falling back on low-level graphics.
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="wpf1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="wpf1"
Width="513"
Height="385"
x:Name="window1"
MouseWheel="window1_MouseWheel">
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="canvas1" Background="#FFFFE8E8"/>
</Window>
...and...
public partial class Window1 : Window
{
double totalScale = 1;
public Window1()
{
InitializeComponent();
for (int i=0; i<60; i++)
{
for (int j=0; j<30; j++)
{
Button newButton = new Button();
canvas1.Children.Add(newButton);
newButton.Width = 25;
newButton.Height = 25;
Canvas.SetTop(newButton, j*30);
Canvas.SetLeft(newButton, i*30);
newButton.Click += new System.Windows.RoutedEventHandler(button1_Click);
newButton.MouseMove += new System.Windows.Input.MouseEventHandler(button1_MouseMove);
}
}
}
Button lastButton;
void button1_Click(object sender, RoutedEventArgs e)
{
lastButton = sender as Button;
lastButton.Background = Brushes.Blue;
}
void button1_MouseMove(object sender, MouseEventArgs e)
{
Button butt = sender as Button;
if (lastButton != butt) butt.Background = Brushes.Yellow;
}
void window1_MouseWheel(object sender, MouseWheelEventArgs e)
{
double scaleFactor = Math.Pow(1.0005,e.Delta);
totalScale *= scaleFactor;
canvas1.LayoutTransform = new ScaleTransform(totalScale, totalScale);
}
}
I have an application where the user draws some shapes.
When I click over a shape and I drag it, the CPU goes 100% because of Invalidate() inside MouseMove.
If I a use a timer and call Invalidate() from tick event the moving is not so smooth.
Is there any other approach to minimize CPU and have smooth moving?
` Point startDragMousePoint;
Point startShapeLocation;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if(isMouseDown)
{
Point deltaPoint = Point.Subtract(e.Location, new Size(startDragMousePoint));
shape.Location = Point.Add(startShapeLocation, new Size(deltaPoint));
Invalidate();
}
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
shape.Render(e.Graphics);
}`
There are three general solutions.
1) Don't draw while your moving, this was the solution in windows for a long time, when you dragged a window, it just disapeard and you saw the outline of a window.
2) Create a bitmap object and only move that. Note you will have to redraw the area under it.
3) Don't invalidate the hole window, just the area you are changing. Drawing to a buffer (a bitmap) can help you reuse areas.
Also, if GDI isn't the fastest drawing functions in the world. If your shape is very complex, you might want to consider using OpenGL, DirectX or SDL.
Instead of invalidating the entire area you could invalidate the portion of the control that has changed by using:
Rectangle changedArea = new Rectangle(cX, cY, cW, cH);
this.Invalidate(changedArea);
Also make sure your control is set to use DoubleBuffering
this.DoubleBuffered = true;
From the limited code that you have put up, I think the invalidate will not cause any problem. Most probably the problem may be inside the real rendering code of yours shape.Render(). In the past I have written similar application where I have called Invalidate in the mouse move and the applications has worked fine. Only there were some flickering which is gone on enabling double buffering.
I'm making a C# inventory application. However, I want there to be a map where the user can click several areas and interact with the inventory on that certain area.
Currently I photoshopped the map and made it the background of the form. I'm planning on putting pictureboxes over the different areas and code manually the mouse over, click, and mouse down events to give the button appearance.
Anyway, my question is, is this a good idea? Should I just load the map into a picturebox, get rid of the buttonish visual effects and track the coordinates of the click?
While I don't think this is a bad idea, one alternative would be to use Rectangles and have an onclick function consisting of a series of Rectangle.Contains(Point) to find out if the point clicked by the mouse is contained in any of the clickable areas.
E.G.
Rectangle R1 = new Rectangle(/*Passed Variables*/);
Rectangle R2 = new Rectangle(/*Passed Variables*/);
//...
OnClick(object sender, MouseEventArgs e)
{
If (R1.Contains(e.Location))
{
//Stuff
}
else
{
If (R2.Contains(e.Location))
{
//Stuff
}
}
}
If you have a larger list of Rectangle objects, though, you could always use an array of Rectangles and a for loop for a more efficient way of checking if the clicked location is inside any Rectangle.
At CodeProject, someone has created an ImageMap Control for this very purpose. I imagine others are available.
I think this would be simple question and should be asked in the pas few years but unable to google around and dont know if there is a specific keyword.
In c# WinForm I want to do drag and drop but I dont want the image of DragDropEffects Move, Copy or whatever. I want to display an image with half opaque. Just like Firefox when dragging an image, you would see the image folowing the mouse pointer like a ghost :)
I already Implement DoDragDrop, DragEnter and DragDrop events. I just want to customize the dragging effects with overlay image.
Might be 9 years too late to the party 😄
I always liked Drag&Drop interactions but found it complicated to use in WinForms. Especially if you want it to look professional with overlays.
I made my own library for WinForms the last time I needed Drag&Drop. You can find it here or on NuGet:
https://github.com/awaescher/FluentDragDrop
Here's everything you need to implement Drag&Drop like shown above:
private void picControlPreviewBehindCursor_MouseDown(object sender, MouseEventArgs e)
{
var pic = (PictureBox)sender;
pic.InitializeDragAndDrop()
.Copy()
.Immediately()
.WithData(pic.Image)
.WithPreview().BehindCursor()
.To(PreviewBoxes, (target, data) => target.Image = data);
// Copy(), Move() or Link() to define allowed effects
// Immediately() or OnMouseMove() for deferred start on mouse move
// WithData() to pass any object you like
// WithPreview() to define your preview and how it should behave
// BehindCursor() or RelativeToCursor() to define the preview placement
// To() to define target controls and how the dragged data should be used on drop
}
I am trying to create this simple application in c#: when the user double clicks on specific location in the form, a little circle will be drawn. By one click, if the current location is marked by a circle - the circle will be removed.
I am trying to do this by simply register the MouseDoubleClick and MouseClick events, and to draw the circle from a .bmp file the following way:
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
Graphics g = this.CreateGraphics();
Bitmap myImage = (Bitmap)Bitmap.FromFile("Circle.bmp");
g.DrawImage(myImage, e.X, e.Y);
}
My problem is that I dont know how to make the circle unvisible when the user clicks its location: I know how to check if the selected location contains a circle (by managing a list of all the locations containig circles...), but I dont know how exactly to delete it.
Another question: should I call the method this.CreateGraphics() everytime the user double-clicks a location, as I wrote in my code snippet, or should I call it once on initialization?
My personal preference is to put my images in instances of the Picturebox class. Reason being, I can simply call each Picturebox's Hide() function (or set 'Visible` to false).
What you're doing is drawing directly onto the window's client area, which technically isn't wrong but normally should be done in the form's Paint handler. If at some point you decide you don't want your circle to be visible anymore, you can call the form's Invalidate() method which triggers the Paint event. There, you explicitly do not draw your circle, and so to the user, the circle disappears.
The nice thing about a Picturebox is that it's persistent - you put your image into it and optionally draw on that image, but you only need to draw once. If you use the Paint handler technique, your drawing code gets called each time the form needs to redraw itself.
Edit:
Here's some code that illustrates my Paint handler information:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(); // clear any and all circles being drawn
if (CircleIsVisible)
{
e.Graphics.DrawEllipse( ... ); // OR, DrawImage( ) as in your example
}
}
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
CircleIsVisible = true;
Invalidate(); // triggers Paint event
}
If you're drawing bitmaps, I would load the bitmap once and store it as a class variable. This way you don't need to hit the hard drive each time you want to draw. Dispose of the bitmap when you dispose of your class (in this case, your window).
I thinks you should clear all of the image you draw before your next double click.
Such as Graphics.Clear().
On the other hand, you should not to create Graphics object or dispose it every time.
If you have simple background color you could use Graphics.DrawEllipse to draw Circles and then just change circle color to the background color. Also you need to have a Collection of all circles you draw so you can access any circle that you've drawn.