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.