Monday, November 30, 2009

Display a Picture on a Flexible Form


This handy pop-up is easy to make, quick to use, and convenient for the user because you can resize the form and the image will retain its shape and size, but even if it doesn't fit inside the form, moving the mouse to the edges will reposition the image to make what you want to see visible. The only problem this bit of code addresses is the need for a quick 'put it out there' way of dropping an image onto the screen which your user can view at leisure. No more, no less.

Using the Code

After referencing the DLL file into your project, instantiating a pop-up form is a simple matter; just look at it as if it were a new variable that required a couple of parameters: an image and a string.
This is what the header for the New() call looks like in the class:

classPicBoxViewer(Image thisImage, string thisText)

You can call it by naming the class, giving your variable a name (in this example, 'thisMarketPrices') which will create a pointer to an instance, so you then need to assign that pointer a value by creating an instance of the class with the keyword 'new', and follow that with the class name again. Only this time, you will need to supply the appropriate parameters (an image and a string). The image will appear inside the new popup form, and the text will appear above the image.

classPicBoxViewer thisMarketPrices = new classPicBoxViewer(
    Supremacy_Warning.Properties.Resources.Market_Prices, "$ $ $");
//if you want the form to resize itself to fit the image then you add the following line

Points of Interest

There's a timer that will scroll the image when the mouse cursor appears close to the edge. Without the timer to slow this process down, the scrolling feature is much too fast on most computers, and your users will likely scroll right past what they're looking for, but since a step is made for every mouse move, you can move the mouse along the edge and accelerate the process, or hold the mouse still and let the timer scroll more slowly. Changing the timer's interval value will affect the rate at which the scrolling occurs. Alternately, you can add a few lines to the mouse move event handler,...

void picTerritoryMap_MouseMove(object sender, MouseEventArgs e) 
... or create a new one for the MouseHover event, so that the size of each step taken depends on how close to the border the mouse is. When you have that working, you can remove the whole timer scheme all together or, better yet, give the user the option of which scheme to use.
The way the whole 'move the picture around' business works is easy enough in theory, but it gets a bit tricky if you don't take the time to think about it properly.
If there wasn't an easier way...

You have to look at the form as a kind of window behind which your picture is visible. The location coordinates of the picture (x, y) are called 'top' for 'y' and 'left' for 'x', and these can be any integer value, positive or negative, where the (0,0) value, or origin, is at the top, left of the form. You can place the picture way off to the side and have it not be visible to the viewer, something you'll probably want to avoid. This flexibility allows you to move the image anywhere you want. Using the 'MouseMove', 'MouseHover', and other mouse event handlers, you can see where the mouse is relative to the picture and that picture's borders. The confusing part here is that though the window is in front of the picture in the sense that you're looking through a window at it (that window I'm referring to here is just the form), the picture is actually in front of the form. Confused? Well, don't worry because you only need to realize that when using the form's 'MouseMove' event, which will give you problems because the mouse is not -directly- in front of the form, it is in front of the picture, which is in front of the form. This subtle difference means that the form's MouseMove event will not handle the mouse moving in front of the picture, so you have to look at the picture's MouseMove event ... but doing this tells you where the mouse is relative to the top left corner of the picture, not the form. Given that you want the picture to move when your mouse cursor approaches the edges of the form and you only know where the mouse cursor is relative to the picture, you'll have to do a bit of math to figure out when your mouse is close to the form's edge or not, and you should probably first draw a diagram on a piece of paper to figure it out.
But, there is a relatively simple solution...

udrMouse = new udtCartesian(); 
udrMouse.X = MousePosition.X - thisForm.Left - 5;
udrMouse.Y = MousePosition.Y - thisForm.Top - 10;
The MousePosition.X (and Y) in these lines is the mouse's location relative to the top left corner of the screen. So here, we create a user-defined-type (UDT) of the 'brand' Cartesian which conveniently stores the (x, y) coordinates. We could alternately have used a Point, and it would have done the same thing. What we're doing here with the udrMouse (user-defined-register) variable is we're calculating the location of the mouse relative to the picture's visible borders. First, we subtract the form's location values thisForm.Left and thisForm.Top for the udrMouse values of x and y, respectively, and then we tweak these values a bit to take in to consideration the top and left edges of the screen, which I find the values of 5 and 10 do nicely. So now, udrMouse.X is exactly how far from the left border of the visible part of the picture the mouse is, and the same can be said about udrMouse.Y and the top border.
Using what we've got ...

We have defined a constant value called conBorderSize which is the width of the region from the edge of the visible image, where we say the mouse is close enough to the edge that we need to move the picture a bit. So, if the mouse's Y value is less than the width of that border (height if you want to be picky), then we're close to the top of the form and need to move the picture down. You'll notice, however, that the value we're changing is udrMapCenter.Y, and we're making that value smaller by subtracting a constant step size named conMapMoveStep. So, if you've got your screen's Cartesian coordinates straight in your head, you know that the Cartesian plane you studied in high-school has the origin in the middle and positive X is to the right and positive Y is up. But your screen's origin is at the top left, which means that though your x's are positive moving to the right, your y's are positive moving down. No worries about this until you try to write the code that will return the proper value for arcTan, but more on that some other time. For now, you only need to realize that as you increase your Y value, you're actually moving down from the top of the screen. So, why are we subtracting from our udrMapCenter.Y value? You may ask, and the answer is because that is the 'center of the visible image', not the location of the actual picture (left and top values) relative to the form. So, we make the center higher up, means the image is to be dropped, and we give the signal to drop the image by setting the boolean bolPlaceMap (you guessed it, bolPlaceMap is an actual boolean; if you've never met one, you'll find that they're a bit bipolar but nice to get along with) to true so that at the end of these 'do we need to move the picture' tests, that value will be set to true and we can place the picture where it needs to be for our user.

if (udrMouse.Y < conBorderSize && udrMapCenter.Y > 0)
  udrMapCenter.Y -= conMapMoveStep;
  bolPlaceMap = true;
else if (udrMouse.Y > thisForm.Height - conBorderSize && 
         udrMapCenter.Y < picTerritoryMap.Width)
  udrMapCenter.Y += conMapMoveStep;
  bolPlaceMap = true;

The only difficult part left in the code above is deciding whether we are at the opposite edge of the screen, but that's not too bad because by now, we know what we're doing! Take the form's width/height into consideration (depending on if you're testing X or Y), and you're done.

See full details:

No comments: