Panels and Styles

IronPython & Windows Forms, Part IV

Windows Forms

Note

This is part of a series of tutorials on using IronPython with Windows Forms.

 

 

Introduction

Ok, so we're getting somewhere. We know how to create an application and use the button widget. Hopefully the examples so far have also given you an idea of how easy it is to read the MSDN documentation and interpret them from a Python point of view.

There is still a lot of ground to cover. In this entry we will look at one of the most basic widgets, the Panel. Unfortunately it is also one of the most boring, because it doesn't actually do anything. Surprised It is however fundamental to almost any GUI. To spice up this entry we will also look at styles. These are some of the ways you can affect the appearance of your program.

The Panel

The Panel is what is often called a container widget. It doesn't do anything itself, and it is rare that you would even want to catch events directly on a panel. What it can do is have other widgets contained in it (hence the container name). So you use the Panel to group widgets together in your GUI. By putting panels side by side, and adjusting how they respond to resizes, you can easily control the layout of your program.

Again from the examples, we can see that the Panel constructor takes no arguments :

Panel panel1 = new Panel();

We add widgets to the panel in the same way as we did for the form. To create a panel with a button in it, and put this on the form, you would do something like the following :

from System.Windows.Forms import Application, Button, Form

class BoringForm(Form):
    def __init__(self):
        panel = Panel()
        button = Button()

        panel.Controls.Add(button)
        self.Controls.Add(panel)

This is so dull that I'm not going to show you a screenshot.

Panels really come into their own when you are using several of them together. In order to be able to see the difference between the different panels we create, we'll look at how to change their colour (and other elements of their appearance).

Style

Panel, like all widgets, inherits from the Control base class. Control defines certain properties related to the style of the widget.

If you browse the list of Panel Members, you can see all of them. For this entry we are going to use :

Notice that for the Text property of the Panel widget, the documentation says :

Overridden. This member is not meaningful for this control.

Not all the members inherited from Control will be meaningful for every widget. It doesn't make sense to get or set the Text property of a panel.

Color

For a panel, the ForeColor will be the colour of any text (labels) on that Panel, it will also be the ForeColor of any widgets you place there.

Both ForeColor and BackColor take System.Drawing.Color structures. You choose which color, by choosing a name from list in the Color Members.

To set a panel foreground color to blue, we need to ensure the following things are in our code :

  • Add a reference to the System.Drawing NameSpace
  • Import the Color name
  • Set the ForeColor or BackColor property on the widget

So, something like this :

import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')

from System.Drawing import Color
from System.Windows.Forms import Application, Form, Panel

class BoringForm(Form):
    def __init__(self):
        panel = Panel()
        panel.ForeColor = Color.Blue

        self.Controls.Add(panel)

There is one thing I don't think I have emphasized yet. So far we have mainly dealt with object properties. A property in .NET means the same thing it does in Python. As well as setting the colour or height of a component, we can also check (get) the property.

from System.Drawing import Color
from System.Windows.Forms import Application, Panel

class BoringForm(Form):
    def __init__(self):
        formHeight = self.Height
        formWidth = self.Width
        print 'Height = ', formHeight
        print 'Width = ', formWidth

It is more normal to change the BackColor of a panel than the ForeColor, you can directly change the colour of child widgets by changing their colour properties.

BorderStyle

Another way we affect the way a container widget is drawn is to use the BorderStyle property. This takes a BorderStyle Enumeration. The members of an enumeration are effectively constants that tells the .NET framework which borderstyle to use.

From the docs, you can see the three different types of border available :

  • Fixed3D A three-dimensional border - BorderStyle.Fixed3D
  • FixedSingle A single-line border - BorderStyle.FixedSingle
  • None No border - BorderStyle.None

So to give a panel a three dimensional border you would need the following code :

from System.Windows.Forms import Application, Form, BorderStyle, Panel

class BoringForm(Form):
    def __init__(self):
        self.panel = Panel()
        panel.BorderStyle = BorderStyle.Fixed3D

        self.Controls.Add(self.panel)

Setting the BorderStyle on a Form is different. You use the FormBorderStyle Property on the form, which takes a FormBorderStyle Enumeration value.

The choices are :

  • Fixed3D - A fixed, three-dimensional border.
  • FixedDialog - A thick, fixed dialog-style border.
  • FixedSingle - A fixed, single-line border.
  • FixedToolWindow - A tool window border that is not resizable. [1]
  • None - No border.
  • Sizable - (The default) A resizable border.
  • SizableToolWindow - A resizable tool window border. A tool window does not appear in the taskbar or in the window that appears when the user presses ALT+TAB.

In a moment we will use all this knowledge in conjunction with Location. Location (as we saw in Part III) lets you specify the position of a widget. This position is relative to its parent widget, with 0, 0 being the top left. This means that a location of 10, 10 is ten pixels down and ten pixels to the right of the top left corner of the parent. The widget is positioned with it's top left corner at the location you specify.

In the past we have used Height and Width to set the dimensions of controls. It can also be done using the Size Property. It takes a Size Structure as the value.

Because the Size Structure has Public Properties, Height and Width it tends to be more verbose to use the Size property. Occasionally you may want to re-use a Size Structure you get from somewhere else (for example an image size), and then it is convenient to set (or get) the Size property.

Hello World 3

Let's wrap this up with some example code that uses all the different things we've covered in this entry.

We will create an application (as usual a subclass of Form) with several child panels.

We will change the size and style of these panels, and position some buttons within them using Location.

For the sake of doing something new, we will set the dimensions of our main application window, based on the size of the screen our user has. Smile

This is easily done from the GetWorkingSize static method of the System.Windows.Form.Screen class. I'm sure you can figure it out from the code below.

This example splits the main form into two horizontal panels. We want the height of each panel to be half the width of the main form. On Windows (but not Mono), if we just use self.Height the total height will include the title bar. To get the right height we use a Form property ClientRectangle. This returns a System.Drawing.Rectangle Structure [2]. In the list of Rectangle Members we can see it has a Height Member. The code example shows us that it is an ordinary integer, so we can use it to get the real height of the form :

public int Height { get; set; }
import clr
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')

from System.Drawing import Color, Point
from System.Windows.Forms import (Application, BorderStyle, Button,
    Form, FormBorderStyle, Label, Panel, Screen)

class HelloWorld3Form(Form):
    def __init__(self):
        self.Text = "Hello World 3"
        self.FormBorderStyle = FormBorderStyle.FixedDialog

        screenSize = Screen.GetWorkingArea(self)
        self.Height = screenSize.Height / 3
        self.Width = screenSize.Width / 3

        self.panelHeight = self.ClientRectangle.Height / 2

        self.setupPanel1()
        self.setupPanel2()
        self.setupCounters()

        self.Controls.Add(self.panel1)
        self.Controls.Add(self.panel2)

    def setupPanel1(self):
        self.panel1 = Panel()
        self.panel1.BackColor = Color.LightSlateGray
        self.panel1.ForeColor = Color.Blue
        self.panel1.Width = self.Width
        self.panel1.Height = self.panelHeight
        self.panel1.Location = Point(0, 0)
        self.panel1.BorderStyle = BorderStyle.FixedSingle

        self.label1 = Label()
        self.label1.Text = "Go On - Press Me"
        self.label1.Location = Point(20, 20)
        self.label1.Height = 25
        self.label1.Width = 175

        self.button1 = Button()
        self.button1.Name = '1'
        self.button1.Text = 'Press Me 1'
        self.button1.Location = Point(20, 50)
        self.button1.Click += self.update

        self.panel1.Controls.Add(self.label1)
        self.panel1.Controls.Add(self.button1)

    def setupPanel2(self):
        self.panel2 = Panel()
        self.panel2.BackColor = Color.LightSalmon
        self.panel2.Width = self.Width
        self.panel2.Height = self.panelHeight
        self.panel2.Location = Point(0, self.panelHeight)
        self.panel2.BorderStyle = BorderStyle.FixedSingle

        self.subpanel1 = Panel()
        self.subpanel1.BackColor = Color.Wheat
        self.subpanel1.Width = 175
        self.subpanel1.Height = 100
        self.subpanel1.Location = Point(25, 25)
        self.subpanel1.BorderStyle = BorderStyle.Fixed3D

        self.label2 = Label()
        self.label2.Text = "Go On - Press Me"
        self.label2.Location = Point(20, 20)
        self.label2.Height = 25
        self.label2.Width = 175

        self.button2 = Button()
        self.button2.Name = '2'
        self.button2.Text = 'Press Me 2'
        self.button2.Location = Point(20, 50)
        self.button2.Click += self.update

        self.subpanel1.Controls.Add(self.label2)
        self.subpanel1.Controls.Add(self.button2)

        self.subpanel2 = Panel()
        self.subpanel2.BackColor = Color.Transparent
        self.subpanel2.Width = 175
        self.subpanel2.Height = 100
        self.subpanel2.Location = Point(220, 25)
        self.subpanel2.BorderStyle = BorderStyle.Fixed3D

        self.label3 = Label()
        self.label3.Text = "Go On - Press Me"
        self.label3.Location = Point(20, 20)
        self.label3.Height = 25
        self.label3.Width = 175

        self.button3 = Button()
        self.button3.Name = '3'
        self.button3.Text = 'Press Me 3'
        self.button3.Location = Point(20, 50)
        self.button3.Click += self.update

        self.subpanel2.Controls.Add(self.label3)
        self.subpanel2.Controls.Add(self.button3)

        self.panel2.Controls.Add(self.subpanel1)
        self.panel2.Controls.Add(self.subpanel2)

    def setupCounters(self):
        self.counterDict = {
            '1': 0,
            '2': 0,
            '3': 0,
        }

    def update(self, sender, event):
        name = sender.Name
        self.counterDict[name] += 1
        label = getattr(self, 'label' + name)
        label.Text = "You have pressed me %s times." % self.counterDict[name]


form = HelloWorld3Form()
Application.Run(form)

There are a few things to notice about this code. It's a bit longer than the last example, but it does nothing that we haven't covered in these examples. The update method does a little trick that uses the Name of the sender widget to work out which button the event came from. This isn't recommended for production code, but saved me having to create three update methods. Smile

The second subpanel is the same colour as panel2. This is because we explicitly set the colour to Color.Transparent, so the colour of the panel beneath shines through.

We set the border style of the form to FormBorderStyle.FixedDialog. This means it is non-resizable, and has no pretty icon in the corner. The form will size itself automatically, depending on your screensize [3].

Because we specify locations relative to the parent widget, the labels and buttons in the subpanels are set to the same Point. This means they are in the same location relative to the top left corner of their respective subpanels.

If you run this program, you will see something that looks like the following monstrosity :

Hello World 3

and :

Hello World 3

So I'm afraid we still haven't yet covered enough to write that Microsoft slaying application, but we're a step closer. Cool

In the next few entries we'll whizz through some more useful widgets, and then look at a better way of doing layout than using Location.

If you're running Mono, then this example will look like this (images thanks to Seo Sanghyeon) :

Hello World 3

and this :

Hello World 3
[1]A tool window does not appear in the taskbar or in the window that appears when the user presses ALT+TAB. Although forms that specify FixedToolWindow typically are not shown in the taskbar, you must also ensure that the ShowInTaskbar property is set to False, since its default value is True.
[2]In our code we use the Rectangle stucture, but we don't initialise a new one. That means we don't need to explicitly import the Rectangle name.
[3]If the form is too small for the panels, then change the form size to use screenSize.Height / 2 and screenSize.Width / 2. I've only tested this on my computer. Razz

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Fri Nov 27 18:32:35 2009.

Counter...