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.
Related
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.
How would I draw something on a Canvas in C# for Windows Phone?
Okay, let me be a little more clear.
Say the user taps his finger down at 386,43 on the canvas. (the canvas is 768 by 480)
I would like my application to be able to respond by placing a red dot at 386,43 on the canvas.
I have no prior experience with Canvas whatsoever.
If this is too complex to be answered in one question (which it probably is), please give me links to other websites with Canvas and Drawing articles.
There are various ways of doing this. Depending on the nature of the red dot, you could make it a UserControl. For a basic circle, you can simply handle your canvas' ManipulationStarted event.
private void myCanvas_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Fill = new SolidColorBrush(Colors.Red);
Canvas.SetLeft(el, e.ManipulationOrigin.X);
Canvas.SetTop(el, e.ManipulationOrigin.Y);
myCanvas.Children.Add(el);
}
I think you need to approach the problem differently. (I'm not including code on purpose, because of that).
Forms and controls in an Windows applications (including Phone) can be refreshed for several reasons, at any time. If you draw on a canvas in response to a touch action, you have an updated canvas until the next refresh. If a refresh occurs the canvas repaints itself, you end up with a blank canvas.
I have no idea what your end goal is, but you likely want to either keep track of what the user has done and store that state somewhere and show it in a canvas on the repaint of the canvas. This could be done with storing all the actions and "replaying" them on the canvas, or simply storing the view of the canvas as a bitmap and reload the canvas with that bitmap when refreshed. But, in the later case I think using a canvas isn't the right solution.
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 make it so that the user can scroll a richtextbox by clicking the window the richtexbox is on and dragging the mouse. Unfortunately I haven't gotten very far:
private void Main_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
}
}
I've seen some suggestions on the web to track the last several x,y coordinates of the mouse and compare them to the x,y coordinates each time the mouse move event is triggered. Is there any less messy way to do this?
None that I know of. Unless you're using an API that handles it for you, you have to keep track of the information manually. And even if you did use an API just for mouse drags, it'd do the storing itself and likely just pass back the current X and Y, and the difference in X and Y, since the API wouldn't know what you want done with the information.
You'd be handling a little bit less information, but saving only about 5 lines or so of code to get the same result.
I am responding to MouseLeftButtonDown events on elements added to a WPF canvas. It all works fine when clicked (i.e. the eventhandler fires off correctly), but it requires too much precision from the mouse pointer. You have to be perfectly on top of the circle to make it work. I need it to be a little more forgiving; maybe at least 1 or 2 pixles forgiving. The elements on the canvas are nice big circles (about the size of a quarter on the screen), so the circles themselves are not too small, but the StrokeWidth of each one is 1, so it is a thin line.
You can see a screenshot here: http://twitpic.com/1f2ci/full
Most graphics app aren't this picky about the mouse picking, so I want to give the user a familiar experience.
How can I make it a little more forgiving.
You can hook up to the MouseLeftButtonDown event of your root layout object instead, and check which elements is in range of a click by doing this:
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(Point, yourLayoutRootElement) as List<UIElement>;
http://msdn.microsoft.com/en-us/library/cc838402(VS.95).aspx
For the Point parameter, you can use the MouseEventArgs parameter e, and call its GetPosition method like this:
Point p = e.GetPosition(null)
I can't remember whether to use HitTest instead of the FindElementsInHostCoordinates. Try both.
http://msdn.microsoft.com/en-us/library/ms608752.aspx
You could create 4 Point objects from the mouse position to create a fake tolerence effect, and call either FindElementsInHostCoordinates or HitTest for all 4 points.
You might want to try to fill the circle with the Transparent colour to make the whole circle clickable...
If that fails, you can also draw helper circles on the same location as the other circles. Make the circle foreground colour Transparent, and make the thickness of the brush a few pixels wider for a more acceptable clickable region around the circle..
Hope this helps!
I think I've done it (with you help to get me started)...
First, I've moved the move event handling to the Canvas instead of each Ellipse. That's good and bad, from an OOP standpoint. At least when the mouse event handling is a responsibility of the HolePattern to set it on up each Hole (the ellipse that is the visual of the Hole), it is abstracted away so that any consumer of my HolePattern will get this functioanality automactically. However, by moving it to the main UI code, I now am dealing with my canvas mouse event at a higher level. But that's not all bad either. We could discuss this part for days.
The point is, I have designed a way to create a "margin of error" when picking something on a canvas with a mouse, and then reading the Hole that the selected Ellipse belongs to, and then I can read the HolePattern that the Hole belongs to, and my entire UI (ListView, textboxes, gridview fo coordinates) are ALL updated by the existing XAML binding, and the Canvas is updated with one call to an existing method to regenerate the canvas.
To be honest, I can't believe I've figured all this out (with your help and others too, of course). It is such a cool feeling to have the vision of this this and see it come to be.
Check out the main code here:
void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int ClickMargin = 2;
Point ClickedPoint = e.GetPosition(canvas1);
Point p1 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y - ClickMargin);
Point p2 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y + ClickMargin);
Point p3 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y + ClickMargin);
Point p4 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y - ClickMargin);
var PointPickList = new Collection<Point>();
PointPickList.Add(ClickedPoint);
PointPickList.Add(p1);
PointPickList.Add(p2);
PointPickList.Add(p3);
PointPickList.Add(p4);
foreach (Point p in PointPickList)
{
HitTestResult SelectedCanvasItem = System.Windows.Media.VisualTreeHelper.HitTest(canvas1, p);
if (SelectedCanvasItem.VisualHit.GetType() == typeof(Ellipse))
{
var SelectedEllipseTag = SelectedCanvasItem.VisualHit.GetValue(Ellipse.TagProperty);
if (SelectedEllipseTag!=null && SelectedEllipseTag.GetType().BaseType == typeof(Hole))
{
Hole SelectedHole = (Hole)SelectedEllipseTag;
SetActivePattern(SelectedHole.ParentPattern);
SelectedHole.ParentPattern.CurrentHole = SelectedHole;
}
}
}
}
Just Increase Stroke ThickNess of the Ellipse so that it is adjustable
thus the MouseLeftButtonDown event works
Example:
In Ellipse tag:
Ellipse
Canvas.Left="10" Canvas.Top="133" Height="24" Name="ellipse1" Width="23" Stroke="Red" MouseLeftButtonDown="ellipse1_MouseLeftButtonDown" ToolTip="Temp Close" StrokeEndLineCap="Flat" StrokeThickness="12"
private void ellipse1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Application curApp = Application.Current;
curApp.Shutdown();
}