Welcome to MSDN Blogs Sign in | Join | Help

Magnify your pictures using a PictureBox so that you can zoom with the Mouse Wheel

Today’s digital cameras take pictures with much higher resolution than many computer screens. My Canon PowerShot SD800 IS camera takes pictures at 3072 x 2204 resolution.

 

One of my laptops died recently, and I noticed that local laptop retailers have machines with 1280 X 1024 resolution. I much prefer a higher resolution display, so I ordered a customizable Dell Inspiron 1525 with 1680 x 1050.

(I’ve been ordering computers from Dell for over 20 years, back in the days when it was called PC’s Limited. Why does Dell distinguish between business/government/home laptops?)

 

I was playing around with some photos using a PictureBox control, and I wanted to add a feature that would allow click/zoom using the mouse wheel to designate a point to magnify.

 

It was pretty easy to create my own class MyPictureBox which inherits from PictureBox and handles the MouseWheel and zooming.

 

Below are C# and VB versions. If I get enough requests, maybe I’ll make a Fox version (although I like this Fox version of zooming: Enable crop and zooming in on your digital photograph display form)

 

Start Visual Studio 2008 (I think these should work in VS2005)

File->New->Project->VB/C#->Windows Application.

 

View->Code, then paste the VB/C# code below, hit F5

 

Move the mouse to designate a zoom anchor point, then mouse wheel to zoom in/out

 

If you switch back to the form designer, the ToolBox has the MyPictureBox control on it, which you can drag/drop onto a form.

 

If you already have your own form that uses a PictureBox or several, you can avoid manually changing all the instances by carefully editing the Form1.Designer.vb file to replace the type with MyPictureBox (make a backup first).

 

As an exercise, try extending this feature by adding the ability to change the anchor point while zoomed or pan the zoomed picture.

 

Some VB/C# coding issues:

·         semicolons

·         capitalization

·         intermediate arithmetic rounding results

·         event handling

 

See also:

Enable crop and zooming in on your digital photograph display form

Create your own media browser: Display your pictures, music, movies in a XAML tooltip

 

<VB Code>

 

Public Class Form1

 

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.Left = 0

        Me.Top = 0

        Me.Size = My.Computer.Screen.WorkingArea.Size

        Dim oPict = New MyPictureBox

        oPict.Size = Me.Size

        oPict.SizeMode = PictureBoxSizeMode.StretchImage

        oPict.Image = New Bitmap("d:\kids.jpg") ' path to some picture

        Me.Controls.Add(oPict)

        Me.ActiveControl = oPict

 

    End Sub

End Class

 

Public Class MyPictureBox

    Inherits PictureBox

    Private zmLevel As Integer = 1

    Private zmPt As Point

    Overloads Property Image() As Image ' we want to hook when client's set the image to reset the zoom level to unzoomed

        Get

            Return MyBase.Image

        End Get

        Set(ByVal value As Image)

            zmLevel = 1 ' reinit

            MyBase.Image = value

        End Set

    End Property

    Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs)

        'MyBase.OnPaint(pe) ' don't call baseclass to paint

        If Me.Image IsNot Nothing Then

            Dim loc As Point

            Dim sz As Size

            If zmLevel <> 1 Then

                sz = New Size(Me.Image.Width / zmLevel, Me.Image.Height / zmLevel)

                ' center on zmPt

                loc = New Point(Me.Image.Width * (zmPt.X / Me.ClientRectangle.Width) - sz.Width / 2, _

                                Me.Image.Height * (zmPt.Y / Me.ClientRectangle.Height) - sz.Height / 2)  '

            Else

                loc = New Point(0, 0)   ' no zoom: we want the entire source picture

                sz = Me.Image.Size

            End If

            Dim rectSrc = New Rectangle(loc, sz)

            ' now draw the rect of the source picture in the entire client rect of MyPictureBox

            pe.Graphics.DrawImage(Me.Image, Me.ClientRectangle, rectSrc, GraphicsUnit.Pixel)

        End If

    End Sub

    Sub PictureBox_MouseWheel(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseWheel

        If Me.zmLevel = 1 Then  ' can only anchor when unzoomed

            Me.zmPt = New Point(e.X, e.Y)

        End If

        If e.Delta > 0 Then

            If zmLevel < 20 Then

                zmLevel += 1

            End If

        Else

            If e.Delta < 1 Then

                If zmLevel > 1 Then

                    zmLevel -= 1

                End If

            End If

        End If

        Me.Invalidate() ' queue msg to repaint

    End Sub

 

End Class

 

</VB Code>

 

 

 

 

 

<C# Code>

 

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication1

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            this.Left = 0;

            this.Top = 0;

            this.Size = Screen.PrimaryScreen.WorkingArea.Size;

            var oPict = new MyPictureBox();

            oPict.Size = this.Size;

            oPict.SizeMode = PictureBoxSizeMode.StretchImage;

            oPict.Image = new Bitmap("d:\\kids.jpg");

            this.Controls.Add(oPict);

            this.ActiveControl = oPict;

        }

    }

}

 

public class MyPictureBox : PictureBox

{

    private int zmLevel = 1;

    private Point zmPt;

    public MyPictureBox()

    {

        this.MouseWheel += new MouseEventHandler(MyPictureBox_MouseWheel);

    }

 

    void MyPictureBox_MouseWheel(object sender, MouseEventArgs e)

    {

        if (this.zmLevel == 1)

        {

            this.zmPt = new Point(e.X, e.Y);

        }

        if (e.Delta > 0)

        {

            if (zmLevel < 20)

            {

                zmLevel += 1;

            }

        }

        else

        {

            if (e.Delta < 1)

            {

                if (zmLevel > 1)

                {

                    zmLevel -= 1;

                }

            }

        }

        this.Invalidate();

    }

    new public Image Image // overrides

    {

        get

        {

            return base.Image;

        }

        set

        {

            zmLevel = 1;

            base.Image = value;

        }

    }

    protected override void OnPaint(PaintEventArgs pe)

    {

        //base.OnPaint(pe);

        if (this.Image != null)

        {

            Point loc;

            Size sz;

            if (zmLevel != 1)

            {

                sz = new Size(this.Image.Width / zmLevel, this.Image.Height / zmLevel);

                // center on zmPt. Casts are needed so integer divide doesn't occur (intermediate double result)

                loc = new Point((int)(this.Image.Width * (zmPt.X / (double)this.ClientRectangle.Width)) - sz.Width / 2,

                                 (int)(this.Image.Height * (zmPt.Y / (double)this.ClientRectangle.Height)) - sz.Height / 2);

            }

            else

            {

                loc = new Point(0, 0);

                sz = this.Image.Size;

            }

            Rectangle rectSrc = new Rectangle(loc, sz);

            // now draw the rect of the source picture in the entire client rect of MyPictureBox

            pe.Graphics.DrawImage(this.Image, this.ClientRectangle, rectSrc, GraphicsUnit.Pixel);

        }

    }

}

 

 

</C# Code>

 

Posted by Calvin_Hsia | 8 Comments
Filed under: , ,

Make your code more maintainable: The evils of the Return statement

What does it mean to make code more maintainable? Certainly obfuscated code is hard to understand, by definition.

 

A big part of maintainability is making it easier for others to read and understand what the code is doing. Your code may have been working for years, but then somebody comes along and wants to add a feature, which might break your code.

 

Sometimes the behavior of code isn’t quite right, so you might need to investigate somebody else’s code.

 

(The repro scenario of the issue might be quite complicated, involving several tedious minutes of (perhaps ambiguous) keystrokes and clicks.

You might be single stepping some code, and suddenly you find yourself past the point where something seems to have gone wrong. <Sigh>. You have to restart the entire scenario, putting a breakpoint before the offending code is executed. Or do you?  Set Next Statement is your friend. Often you can use Set Next Statement to go back a bit. You can perhaps modify some local variables and try to execute the code again, without having to repeat the tedious repro scenario.)

 

The Return statement exists in many languages, including C#, VB, FoxPro, C++. This discussion pertains to all of them.

 

Each of these languages allow you to create a very complex expression, perhaps with inline conditionals, precede it with the keyword “Return” and the value of the expression is the return value of the current method.

 

 

Suppose you had “simple” functions Foo and Bar:

 

int Foo(someparam)

{

return Bar(someparam)->SomeMethod(SomeOtherParam);

}

 

int Bar(someparam)

{

            <...some code...>

            return AnotherFoo(SomeFunc(SomeOtherValue));

}

 

Another return statement sample with 20 calls in it:

 

    return (HasFlag(Flags, GetSomeVal(Flags)) &&

        !HasFlag32(Result, SXF_LPE) &&

        !(IsSomeValPointer(Result) &&

          // Rule out Pointers to values of types.

          !(Result->AsSomeValPointerExpression().Left->type == type_WRD &&

            Result->AsSomeValPointerExpression().Left->AsWordPointerExpression().BasePointer &&

            !HasFlag32(Result->AsSomeValPointerExpression().Left->AsWordPointerExpression().BasePointer, SXF_LWORD) &&

            ValHelpers::IsValue(Result->AsSomeValPointerExpression().Left->AsWordPointerExpression().BasePointer->ResultType)&&

            // make sure SomeSomeVal() still works for value types(bug 32804)

            !(Result->AsSomeValPointerExpression().Left->AsWordPointerExpression().BasePointer->type == type_SYM &&

             Result->AsSomeValPointerExpression().Left->AsWordPointerExpression().BasePointer->AsWordPointerExpression().Word->PVariable()->IsGood()))))

 

 

 

 

 

Suppose you’re debugging a program and single stepping and reach the end of Bar(). Even after stepping over this statement, you can’t easily see the value (or intermediate values) that’s being returned. You can’t even see the type of the return value unless you can see the method signature on the screen:  “Function MyMethod() As SomeType”

 

The Autos window does show the return value/type for native code, (but not VB or C#), but I find that the other info it displays is useless. It tries to guess the nearby values that you might want to see: I much prefer the Locals window. It would be nice if the return value from the Autos window were displayed in the Locals window. So I’ve resorted to showing just the top couple of lines of the Auto window under my Locals window. However, the Autos window doesn’t allow you to change a returned value, whereas the Locals window does.

Also, seeing intermediate values by hovering your mouse works for local values, but not for method calls.

 

(If you’re debugging C++ code, then if you open the Debug->Windows->Registers window, you can see and change simple return values like Bools and Integers in the EAX register (32 bit) or RAX (64 bit), but watch out for implied destructor calls)

 

 

So you step out of the function Bar(), then you’re on another complex return statement in Foo(), where you still can’t see the returned value. So you step out again, and yet another Return statement. You might need to step out a few levels before you can see what the value is. If you want to reexamine this path, you can Set Next Statement back, but then you’re at a call stack level that’s far from the original code, so you have to go drill back down the callstack.

 

Now suppose you think the return value of Bar() isn’t quite right, or you want to reexamine what is happening.

 

Right click and choose “Step Into Specific” ,which might list 20 different functions into which you could step. However, depending on conditionals and prior values, not all of the 20 may be called. So you Step into the first, see that it’s uninteresting, Step into the Next, etc. until you see something interesting.

 

(look for a future blog post on Just My Code for native debugging that will remove a lot of the uninteresting Step Intos., like library or template functions or operator overloads)

 

 

So we’ve discussed two maintainability issues:

·         make the return value of your function more visible while debugging, so the user can verify functionality more easily and can use Set Next Statement.

·         Break up complex expressions, especially in Return statements.

 

 

The simple solution to both of these issues: Introduce new local variables to have intermediate results and break up nested conditional clauses to be separate If statements.

 

·         Set Next Statement and Breakpoint granularity is finer.

·         Intermediate results can be seen and modified in the Locals window

·         Mouse hover shows the values

·         The local variable names can be further indications of intent.

 

 

Simply changing the code from:

            Return <ComplexExpr>

To

            Int RetVal = <ComplexExpr>

            Return RetVal

 

will make a huge difference in this debugging scenario. The user can Step Over and the RetVal can be seen in the debugger and, if it’s wrong, the user can Set Next Statement back and reexecute the code, Stepping In, rather than Stepping Over (watch out for side effects of reexecution). The user stays within the method without having to back up a few levels of the call stack. Also, it’s easier to see the type of the return value.

 

int Foo(someparam)

{

            SomeType Barval = Bar(somparam);

            Int RetVal = Barval->SomeMethod(SomeOtherParam);

 

return Retval;

}

 

int Bar(someparam)

{

            <...some code...>

            SomeFuncType SomeFuncVal = SomeFunc(SomeOtherValue));

            Int Retval = AnotherFoo(SomeFuncVal);

            return Retval;

}

 

 

 

Modern optimizing compilers can optimize out those variables and can generate identical code.

 

I once heard of a chess program written in one (very long) line of code, but imagine trying to maintain that puppy!

 

 

 

 

 

Cellular Automata: The Game of Life

I remember my dad getting a copy of Scientific American magazine every month when I was a kid. He kept back issues on a shelf in our living room. I remember being fascinated by the issue (Oct 1970) which described John Conway’s Game of Life.

 

A few decades later, in the mid 80’s I wrote my own version using the DeSmet C compiler on an original IBM PC (see Why was the original IBM PC 4.77 Megahertz?).  I remember playing with gliders, glider guns and other formations.

 

A couple decades after that, I wrote one in C# (the date is 2003). Below is my original version from 2003, with one line of code added to make it run with more recent versions of VS.

 

(A subsequent article in Scientific American (Dec 84) described a cellular automata that simulated the Predator Prey ecosystem called Wator by Alexander Dewdney. This inspired me to write my own version of Wator in C++.)

 

Start VS 2008. File->New->Project->C#->Windows Forms

 

Edit the Program.cs code and replace it with the code below, then hit F5. Try moving the mouse around. Button1 results in an error.

 

The  one line I had to add to get it to work was:

            Form.CheckForIllegalCrossThreadCalls = false;  

 

 

Without that line, an exception would occur:

System.InvalidOperationException was unhandled

  Message="Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on."

  Source="System.Windows.Forms"

  StackTrace:

       at System.Windows.Forms.Control.get_Handle()

       at Life.Form1.DrawCells(Boolean fDrawAll) in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 47

       at Life.Form1.Shuffle() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 90

       at Life.Form1.Generations() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 184

       at Life.Form1.MyThread() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 304

       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       at System.Threading.ThreadHelper.ThreadStart()

  InnerException:

 

Looking at the code, I see that indeed I wrote code that created a background thread that drew to the hWnd directly. So I added the code to set CheckForIllegalCrossThreadCalls  false.

 

<code sample>

 

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.Threading;

using System.Runtime.InteropServices;

 

namespace Life

{

    /// <summary>

    /// Summary description for Form1.

    /// </summary>

    public class Form1 : System.Windows.Forms.Form

    {

        /// <summary>

        /// Required designer variable.

        /// </summary>

        [DllImport("kernel32.dll")]

        public static extern bool Beep(int freq, int dura);

        private System.ComponentModel.Container components = null;

        private TextBox m_box;

        private Button m_button;

        private Thread m_Thread = null;

        bool m_nReset = false;

        static int m_numx = 200;

        static int m_numy = 200;

        static int m_cellx = 5;

        static int m_celly = 5;

        static int m_Offx = 10;

        static int m_Offy = 10;

        static int MAXCOLOR = 8;

        static int COLORDIV = 16;   // # of gens before color changes

        bool m_fDrawAll = false;

        SolidBrush m_brushBlack = new SolidBrush(Color.Black);

        SolidBrush m_brushWhite = new SolidBrush(Color.White);

        SolidBrush[] m_brushArray = new SolidBrush[MAXCOLOR];

        Queue m_queue = new Queue();

        int[,] m_cells = new int[m_numx, m_numy];

        private System.Windows.Forms.Button button1;

        int[,] m_diff = new int[m_numx, m_numy];

 

        private int DrawCells(bool fDrawAll)

        {

            int nTotal = 0;

            Graphics g = Graphics.FromHwnd(this.Handle);

 

            int x, y;

            if (fDrawAll)

            {

                System.Diagnostics.Debug.Write("\nDraw");

            }

 

            for (x = 0; x < m_numx; x++)

            {

                for (y = 0; y < m_numy; y++)

                {

                    if (fDrawAll || m_diff[x, y] == 1)

                    {

                        if (m_cells[x, y] == 0)

                        {

                            g.FillRectangle(m_brushWhite, m_Offx + x * m_cellx, m_Offy + y * m_celly, m_cellx - 1, m_celly - 1);

 

                        }

                        else

                        {

                            nTotal++;

                            g.FillRectangle(m_brushArray[m_cells[x, y] / COLORDIV], m_Offx + x * m_cellx, m_Offy + y * m_celly, m_cellx - 1, m_celly - 1);

                        }

                    }

                }

 

            }