How do I do the following I'm not asking for specific code but I need some direction as I have been racking my brains on this for weeks. Simply I want to make a map of the eg. united states and each state is a different picture or area that can be moused over and clicked. I tried playing with png and transparencies but I'm at a dead end. More ambitiously I'd like to drag labels with state capitals over each state and drop them there then have a process where if the label/capital matches the state it correct else it's not.
I've tried GIS(?) I want to do this C# but I can't get traction how to do it so far. Can anyone help? Is this too difficult in C#? SHould i be using another approach? Please what is the approach?
The good news first: The programming part is not so hard and even with good old Winforms you can do the core checks in a few lines. The clickable areas can't be Buttons in Winforms, however.
Here are two solutions:
Solution 1 : You can define a list of areas, called regions and test if one got clicked.
Here is a start: I define a very simple Rectangle Region and a not so simple Polygon Region and test on each click if one was hit. If it was hit I output its data into the Form's title text:
//..
using System.Drawing.Drawing2D;
//..
public Form1()
{
InitializeComponent();
// most real states don't look like rectangles
Region r = new Region(new Rectangle(0, 0, 99, 99));
regions.Add(r);
List<int> coords = new List<int>() {23,137, 76,151, 61,203,
117,283, 115,289, 124,303, 112,329, 76,325, 34,279, 11,162};
List<Point> points = new List<Point>();
for (int i = 0; i < coords.Count ; i += 2)
points.Add(new Point(coords[i], coords[i+1]));
byte[] fillmodes = new byte[points.Count];
for (int i = 0 ; i < fillmodes.Length; i++) fillmodes[i] = 1;
GraphicsPath GP = new GraphicsPath(points.ToArray(), fillmodes);
regions.Add(r);
}
List<Region> regions = new List<Region>();
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
using (Graphics G = pictureBox1.CreateGraphics() )
foreach(Region r in regions)
{
Region ri = r.Clone();
ri.Intersect(new Rectangle(e.X, e.Y, 1, 1));
if (!ri.IsEmpty(G)) // hurray, we got a hit
{ this.Text = r.GetBounds(G).ToString(); break; }
}
}
The regions in the test programm are not visible. For testing you may want to add something like this:
private void button1_Click(object sender, EventArgs e)
{ // paint a non-persistent filling
using (Graphics G = pictureBox1.CreateGraphics())
foreach (Region r in regions)
{ G.FillRegion(Brushes.Red, r); }
}
But now for the bad news: You can define regions to be complicated polygons that look like real states, however that is a lot of work! There are ready-made image map editors out there in the web for making clickable maps on websites. Your best course might be to use one of these to create all those polygons and then convert the html output to a list of points you can read into your program. You may even get lucky and find ready image maps for the USA, but I haven't looked..
Update: There are many image maps of the USA out there. One is even on wikipedia. Converting these coordinates to fit your map will be a task but lot easier than creating them from scratch, imo. I have changed the code to include one list of cordinates from the wikipedia source. Guess the state!
I have implmented a program that lets you click on a state and displays the name in 78 lines of code, excluding the text file with the 50 states.
Solution 2:
Instead of a list of polygons you can prepare a colored map and use the colors as keys into a Dictionary<Color, string> or Dictionary<Color, StateInfo>. You must make sure that each state has one unique color and that the image is not compressed as jpeg, which would introduce artifacts and mess up the key mapping.
Next you map each color to the related info; this is the real work because you must know those states to create the Dictionary ;-)
Finally you can look up the clicked color in the Dictionary you have created:
Color c = ((Bitmap) pictureBox1.Image).GetPixel(e.X, e.Y)
string StateName = stateDictionary[c];
If you are using a class or struct as your value in the dictionary you can include the capital etc..
You can zoom but must scale the click location accordingly; you can even display a geographical Image if you look up the key color not in the PictureBox but in an invisible color code bitmap.
All in all an even simpler solution imo.. your pick!
Once you got that working, you can play with drag&drop. Here you'll want to test on DragDrop or maybe on Mouseup to see where you have dropped the Label.
This is a nice project and well worth of creating a few classes to make things easier and more expandable..!
For real Buttons you may get lucky with WPF.
Related
I'm trying to build a simple RPG using the C# Windows Forms (as I had recently stumbled upon a fun tutorial demonstrating this ability. I have two items:
A 'Character' object that has been placed IN game:
In-game character--GUI
I also have a code-generated Draw Object, a tree--built in-game:
public void MainFormPaint(object sender, PaintEventArgs e)
{
//Drawing a tree, to create transparency
Image Tree_2 = Image.FromFile("[Directory to PNG].png");
Tree_2.Tag = "Tree";
e.Graphics.DrawImage(Tree_2,50,50,200,200);
}
...which generates this:
Code-generated Tree Object
Seeing as I cannot detect the object by some means similar to:
Character.Bounds.Intersectswith([insert_my_picture].Bounds);
this leaves me kind of baffled, and I'm not sure what to do. I want to detect this collision, so that I can stop movement. However, I'm not sure how to check for an 'empty spot' next to me or any object for that matter that's code-generated. It's important to note that this image is code-generated to maintain the graphic's transparency (as apparently there are problems placing objects in the form and maintaining transparency with overlaying objects).
Thank you for your help!
As the graphic was drawn at runtime, I was needing to generate a Rectangle:
Rectangle firstTree = new Rectangle();
...in the public variables area, and then created it on paint event.
public void MainFormPaint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(Tree_Obj,50,50,200,200);
firstTree.X = 50;
firstTree.Y = 50;
firstTree.Width = 200;
firstTree.Height=200;
}
The full, encapsulating bounds were only for trial. Problem solved!
and thanks for taking a peek at my conundrum.
I am trying to write a series of strings to an image as overlays, then later be able to come back and move, or delete one of them selectively using WPF framework...
I've been looking into the FindVisualChildren function, but for the moment cant make heads or tails of how to detect the proximity to the mouse (for selectivity), or actually detect one of my created strings (Perhaps I should be making them dynamic 'Label' elements...????)
Insomnia sucks, and my brains are turning to mush.
TIA for any advice!
Okay, sorry for the lack of sample code, been a long night... well two nights actually (See earlier comment about insomnia)
public void WriteTextToImage(Point position)
{
ImageSource bitmapSource = imgFrame.Source;
var vis = new DrawingVisual();
using (DrawingContext drawingContext = vis.RenderOpen())
{
// Set the pen color... Why is this called a brush if it's for
// writing? perhaps I should overload it and call it crayon?
SolidColorBrush brush = new SolidColorBrush((Color)cpColor.SelectedColor);
drawingContext.DrawImage(bitmapSource, new Rect(0, 0, imgFrame.Source.Width, imgFrame.Source.Height));
//Write some pretty words, (actually print some general stuff)
drawingContext.DrawText(new FormattedText(tbCurrentLabel.Text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Arial"), slFontSize.Value, brush),position);
}
//Slap this puppy to the screen
var image = new DrawingImage(vis.Drawing);
//Iterate the label text to the next digit or value
IterateLabel();
}
Basically what will happen is the user will click the screen in several places to make marks on the image for printing, but I want to include support to move those marks, and delete them as needed.
I hope this explains what I am trying to accomplish a little better.
Thanks again!
Excellent! thanks NineBerry, I figured it was going to be something like that. I was originally thinking a label, but the textblock worked perfectly.
Here's the updated code showing how I am getting it done now.
public void WriteTextToImage(Point position)
{
SolidColorBrush brush = new SolidColorBrush((Color)cpColor.SelectedColor);
//Get something to write on (not an old receipt...)
TextBlock textBlock = new TextBlock();
//Say something useful... well something atleast...
textBlock.Text = tbCurrentLabel.Text;
textBlock.FontSize = slFontSize.Value;
textBlock.Foreground = brush;
Canvas.SetLeft(textBlock, position.X);
Canvas.SetTop(textBlock, position.Y);
canvas.Children.Add(textBlock);
//Need to update the canvas, was not seeing children before doing this.
canvas.UpdateLayout();
//Iterate the label text
IterateLabel();
}`
In my setup i have 5 pictureboxes which I am trying to layer.
My code for this is:
pbCoin1.Parent = pbMap;
pbCoin1.BackColor = Color.Transparent;
pbCoin2.Parent = pbMap;
pbCoin2.BackColor = Color.Transparent;
pbCoin3.Parent = pbMap;
pbCoin3.BackColor = Color.Transparent;
pbFlashlight.Parent = pbMap;
pbFlashlight.BackColor = Color.Transparent;`
All 5 pictureboxes contain images. The method I am using works fine, but the problem is that the PbCoin 1,2,3 are glitching trough my pbFLashlight(see image).
Can someone provide a solution such that the coins are only visible when the transparent part of the black layer is over it?
Don't use multiple picture boxes. Use a single PictureBox to represent the game area and handle the .Paint event. Do all of your drawing using GDI calls on the Graphics reference that .Paint sends you in e. You will want to call PictureBox.Invalidate at the end of the Paint event to cause it to queue up the next frame
For better performance, create a new Bitmap instance that you keep a reference to for the life of the game. Clear and do all your drawing to that bitmap every frame, then draw that bitmap to the PictureBox in .Paint.
For even better performance than that, don't use WinForms.
The basic pattern should look like this:
private void Canvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(...);
Canvas.Invalidate();
}
I would like to show 1 million locations on a map based on OpenStreetMap.
I work on C# VS2013 and GMAP.NET WPF. But, when I added markers for each location, the map cannot be shown up because the marker is a bitmap image.
And 1 million markers consume too much memory on my laptop (with 8 GB mem).
The code is:
public void add_marker(List<Tuple<double, double>> latLongList, ref GMapControl myMap)
{
System.Windows.Media.Imaging.BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(#"C:\djx_2014_6_3\my_projects\test_gmap_dot_net\GMap_WPF\try1\try_gmap_wpf\try_gmap_wpf\images\map_marker.png", UriKind.Absolute);
bitmapImage.DecodePixelHeight = 5;
bitmapImage.DecodePixelWidth = 5;
bitmapImage.EndInit();
foreach(var v in latLongList)
{
GMap.NET.PointLatLng point = new GMap.NET.PointLatLng(v.Item1, v.Item2);
GMapMarker marker = new GMapMarker(point);
System.Windows.Controls.Image image = new System.Windows.Controls.Image();
image.Source = bitmapImage;
marker.Shape = image;
marker.ZIndex = 5;
myMap.Markers.Add(marker);
}
}
I do not want to use the image as markers but I cannot find out how to use default marker in openStreetMap.
Any help would be appreciated.
WPF wasn't designed to be used for things like this. First of all you're creating a Bitmap for each tag, which is a user control and comes with some pretty heavy overhead for GUI hit-testing and binding etc. Secondly, WPF renders with DirectX, which means at some point all that data has to be set up with vertex buffers and uploaded into the graphics card. If you use data binding or try to create separate UI elements then this is going to take a lot of initial set-up time and memory, as you have already discovered. And if you try to draw them yourself (e.g. by creating your own user control and overriding OnRender) then it can be even worse, since all that work is now being done every frame (apart from buffered stuff which still incurs the initial setup anyway so you're back to square one).
If I had to do this myself I would start by organizing the data set with an appropriate 2D structure such as a k-d tree, an R*-tree or even a basic quad-tree. This will allow you to quickly determine at any given moment which markers are in the view frustum.
Next, I would add the appropriate bindings to scrollbars etc so that I could monitor exactly where the current view frustum was, and then each frame I would update the list of visible tags based on that. So long as you don't have more than a few thousand objects comes into view at once you should be ok, otherwise you'll have to stagger the updates over multiple frames with a queue.
If that doesn't suit your needs then you really have only two options left: 1) generate the raw bitmap data yourself manually, or 2) use a more suitable technology.
I would like to make a map that shows each state, when hovering over a certain state, the respective shape would change color and some information about it would appear.
Here is a web-based example of something similar
kartograph.org/showcase/usa-projection
Using .NET 4.5, C#, and WinForms is it possible to achieve this with a Button and handling mouse events?
This isn't a complete answer, but might put you on the right path.
WinForms won't let you use the Button object in this way; WinForms buttons are quite limited in their ability to be customized--WPF would likely lend itself to this, if it's an option.
To do this in WinForms it's likely that you'll need to use GDI and load each state into it's own Graphics object and write your own plumbing for click events and such. While I can't offer a specific example it should be feasible, but it's also likely to be a fair amount of work (especially for things such as transparent parts of the image).
However, if you either look into WPF or into interacting with GDI objects you should be able to make progress.
This answer completely ignores your question about creating buttons with funny shapes and instead only deals with building something like the example you showed a link to: Identifiying a state on a map by clicking or hovering with the mouse.
To identify the state is simple:
If you can assign each state a color (even if it is only very slightly different) you could use GetPixel to check on which country/color the mouse has clicked or is hovering..
If you don't want visible colors you can still use the same trick by simply using two overlaid maps and show the top map while using the colored one below as a lookup table.
Of course you don't even need to put the lookup map into a control; you can simply use its Bitmap as long it has the same size as the visible map.
Won't take more than a few lines of code in winforms. Setting up the list of states and filling them with colors will be more work.
To change its color is more tricky. I think I would use a quite difffrent approach here: Coding a floodfill algorithm is pretty simple; wikipedia has a few nice ones, expecially the one without recursion (queue-based) is really simple to implement.
So you could use an overlaid copy of the map and floodfill the state the mouse hovers over.. (For this to work, you need to make sure the states can be floodfilled, i.e. that they have closed outlines. This can be part of the preparation of coloring them.)
When the mouse moves to a different state/color you would restore the original map..
Your example has a nice, if somewhat slow, animation. This would be even trickier. If you need that maybe WPF is really worth considering.. Although a coloring animation is doable in Winforms as as well, maybe with a Color Matrix and a Timer it certainly wasn't built for glitz..
Here is a piece of code that goes at least half the way:
// simple but effective floodfill
void Fill4(Bitmap bmp, Point pt, Color c0, Color c1)
{
Rectangle bmpRect = new Rectangle(Point.Empty, bmp.Size);
Stack<Point> stack = new Stack<Point>();
int x0 = pt.X;
int y0 = pt.Y;
stack.Push(new Point(x0, y0) );
while (stack.Any() )
{
Point p = stack.Pop();
if (!bmpRect.Contains(p)) continue;
Color cx = bmp.GetPixel(p.X, p.Y);
if (cx == Color.Black) return;
if (cx == SeaColor) return;
if (cx == c0)
{
bmp.SetPixel(p.X, p.Y, c1);
stack.Push(new Point(p.X, p.Y + 1));
stack.Push(new Point(p.X, p.Y - 1));
stack.Push(new Point(p.X + 1, p.Y));
stack.Push(new Point(p.X - 1, p.Y));
}
}
}
// create a random color for the test
Random R = new Random();
// current and last mouse location
Point mouseLoc = Point.Empty;
Point lastMouseLoc = Point.Empty;
// recognize that we have move inside the same state
Color lastColor = Color.White;
// recognize the outside parts of the map
Color SeaColor = Color.Aquamarine;
// start a timer since Hover works only once
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
mouseLoc = e.Location;
timer1.Stop();
timer1.Interval = 333;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
// I keep the map in the Background image
Bitmap bmp = (Bitmap)pictureBox1.BackgroundImage;
// have we left the image?
if (!new Rectangle(Point.Empty, bmp.Size).Contains(mouseLoc)) return;
// still in the same state: nothing to do
if (lastColor == bmp.GetPixel(mouseLoc.X, mouseLoc.Y)) return;
// a random color
Color nextColor = Color.FromArgb(255, R.Next(255), R.Next(255), R.Next(256));
// we've been in the map before, so we restore the last state to white
if (lastMouseLoc != Point.Empty)
Fill4(bmp, lastMouseLoc,
bmp.GetPixel(lastMouseLoc.X, lastMouseLoc.Y), Color.White );
// now we color the current state
Fill4(bmp, mouseLoc, bmp.GetPixel(mouseLoc.X, mouseLoc.Y), nextColor);
// remember things, show image and stop the timer
lastMouseLoc = mouseLoc;
lastColor = nextColor;
pictureBox1.Image = bmp;
timer1.Stop();
}
All you need to run it is a PictureBox pictureBox1 and a Timer timer1 and a version of the map that has only 3 colors: Black, White and Aquamarine.
What it will do is paint the state you hover over in a random color.
Your next steps would be to create a list of all states with a number, title and info text. Then you create a 2nd version of the map where you color each state with a a color you derive from the state's number.
You can use the code above for the coloring, if you expand it a little.
Finally you code a lookup in the Tick event to get the info to display in a Tooltip..
Of course this is assuming that you are satisfied with working with the map as a Bitmap. The source to link to uses a SVG file, where all data are stored as vector data in an XML format. Parsing this to get Points for a GraphicsPath is also an option, which will then work in the vector realm. But I guess it could take a few days more to build..
My finished, rough version including the code to create the color map and the code for doing lookups comes in a ca. 150 lines, without comments.