0

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?

STW
  • 44,917
  • 17
  • 105
  • 161
  • 2
    What are you using? Winforms? WPF? – Omada Feb 06 '15 at 19:51
  • 1
    Welcome to StackOverflow, it's hard to provide an answer based on your question. What exactly are you trying to achieve? Are you using WPF, WinForms, web? What framework version--and what have you tried? – STW Feb 06 '15 at 19:52
  • A button in what context? A WPF or Windows store app? Use blend to trace a png into a shape. – OakNinja Feb 06 '15 at 19:52
  • 1
    http://stackoverflow.com/help/how-to-ask – STW Feb 06 '15 at 19:52
  • Thank you! I am familiar only with the WinForms for now, using vs 2012 with 4.5 framework..I am trying to build a country map composed of multiple states..when hovering over a certain state, the respective shape would change color and some information about it would appear..I can't say I tried very much, besides trying to change the picturebox shape or trying to create a form with that shape... http://kartograph.org/showcase/usa-projection/ .. i want to make something like this.. is it possible? – Vlad Prundeanu Feb 06 '15 at 20:06
  • Thanks for clarifying, I've edited your question to include the details in your comment--feel free to edit it again to make sure it's asking what you need to know. Take a little while to read the "help" for stackoverflow, it'll help get you answers to your questions quickly. – STW Feb 06 '15 at 20:16
  • Thanks..although i'm not new to stackoverflow, i'm new in asking for help, besides the obvious lack in programming knowledge.. – Vlad Prundeanu Feb 06 '15 at 20:19
  • 1
    It's technically possible in WinForms, but I don't recommend doing it. You'd could represent each state as a GraphicsPath() and then create a Region() from it that gets assigned to the Region() property of your Button to make it non-rectangular. There are other ways you could do it as well...nothing very clean or easy. This is definitely a task better suited to WPF... – Idle_Mind Feb 06 '15 at 21:50
  • you dont wan't a button... you want a costum control that does hit testing based on data... – AK_ Feb 07 '15 at 13:02

2 Answers2

2

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.

STW
  • 44,917
  • 17
  • 105
  • 161
  • I didn't wanted a button in particular, but something to work..I think I'm going to take your advice and use WPF, but I have to familiarize myself with it first..thanks for your help – Vlad Prundeanu Feb 06 '15 at 20:37
0

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.

TaW
  • 53,122
  • 8
  • 69
  • 111