Python Programming, news on the Voidspace Python Projects and all things techie.

Buttons in a Silverlight DataGrid Header

emoticon:python Today I was hoping to complete adding an excel-like auto-filter to the Silverlight DataGrid which is used for the main view throughout a big chunk of our application. Instead I spent almost the entire day just getting the first step working - putting a button in the grid header and wiring up the click event.

Generally using Silverlight from IronPython is a pleasant experience, but there are one or two things that can't be done through code and have to be done in XAML. XAML is the markup language used to describe Silverlight user interfaces; the Silverlight UI is a cutdown mostly-subset of Windows Presentation Foundation (WPF), the hardware accelerated Windows [1] user interface framework that is part of .NET 3 and above.

With WPF you can not only define the visual elements (including animations) of a UI, but also setup data-binding and bind handlers to events. Event handlers are statically bound and this is a problem for IronPython. Even though we can now use clrtype to create true .NET classes from IronPython, the XAML loader uses Type.GetType() to find the types - and this fails with generated classes.

For almost everything this isn't a problem as we can just hook up events from code, but creating custom DataGrid headers and hooking up events to controls in them is one of the places where we can't.

So the first part of the puzzle is creating a custom style in UserControl.Resources and setup the ContentTemplate:

<Setter Property="ContentTemplate">
    <Setter.Value>
        <DataTemplate>
            <Grid Height="{TemplateBinding Height}" Width="Auto">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <StackPanel Orientation="Horizontal" Margin="2"
                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                    <TextBlock Text="{Binding}" HorizontalAlignment="Center"
                     VerticalAlignment="Center" Margin="0.2"/>
                    <Button Content="Foo" Margin="5,0" x:Name="filter" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Setter.Value>
</Setter>

See how the DataTemplate contains a Grid with sub-ui-elements like a TextBlock and the oh-so-important Button. The {Binding} in the textblock means that the standard header text is displayed alongside our button. The XAML for the DataGrid specifies the style we created:

<data:DataGrid x:Name="dataGrid" CanUserReorderColumns="False"
    ColumnHeaderStyle="{StaticResource OurColumnHeaderStyle}"
    IsReadOnly="True" AutoGenerateColumns="False" />

Ok, so far so good. When the columns are created for our grid they will use the ContentTemplate to create the column headers:

A Silverlight DataGrid with custom headers

If we'd been using C# we could have hooked up the button click event in the style XAML. So how do we do this from IronPython? This is what took most of the day to work out. The column headers aren't exposed in any straightforward way, but once the columns have been created we can 'walk the visual tree' to find the column header object, and from there we can find the button. Walking the visual tree is done with VisualTreeHelper, and a recursive helper function:

from System.Windows.Media import VisualTreeHelper

def find_children(parent, findtype):
    count = VisualTreeHelper.GetChildrenCount(parent)
    for i in range(count):
        child = VisualTreeHelper.GetChild(parent, i)
        if isinstance(child, findtype):
            yield child
        else:
            for entry in find_children(child, findtype):
                yield entry

We use it to find and hook up the buttons thusly:

from System.Windows import Visibility
from System.Windows.Controls import Button, DataGridTextColumn

for entry in find_children(datagrid, DataGridColumnHeader):

    for button in find_children(entry, Button):
        if not button.IsEnabled:
            button.Visibility = Visibility.Collapsed
        else:
            button.Click += handler_function
        # just one button per column
        break

We make disabled button invisible because otherwise the grid will leave a disabled button visible in the header to the right of the populated columns.

So that was the hard part. The next problem wasn't quite so difficult; inside the event handler how do we know which column the button click is for? It turns out that getting a reference to the column inside the click event handler is easy:

from System.Windows.Controls import DataGridColumn

def handler_function(sender, event):
    column = DataGridColumn.GetColumnContainingElement(sender)
    ...

At least next time I have to do this is it will be a bit easier... Smile

The final version will use an image for the button content, but the hard part is done.

UPDATE

My colleague Stephan Mitt tells me that the only reason it was so easy to get the column from the button event is that he spent three hours previously discovering this API. Props to him for working this out and making my life easier. Stephan also wanted me to show you the end result with his nice filter images instead of my proof-of-concept 'Foo' buttons:

A Silverlight DataGrid with custom headers using image buttons

The code to set the image on the button looks like this:

from System import Uri, UriKind
from System.Windows.Controls import Image
from System.Windows.Media.Imaging import BitmapImage

def click_handler(sender, event):
    uri = Uri('images/someimage.jpg', UriKind.RelativeOrAbsolute)
    bitmap = BitmapImage(uri)
    image = Image()
    image.Source = bitmap

    # The sender is the button
    sender.Content = image

As I was integrating this with our production code I encountered another problem. Many of the grids we used are loaded into TabControl pages, and the headers aren't created until the grid is rendered (the tab page the grid is in is selected). This means it matters when you run the code that walks the visual tree to find the buttons and setup the click handlers. A good place to do it is in response to the Loaded event, which is fired after the headers have been created.

[1]Silverlight runs on both the Mac and Windows however, and I do almost all my development on the Mac. With Moonlight, Silverlight apps can run and be developed on Linux too.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2010-03-17 23:49:04 | |

Categories: , , Tags: ,


EuroPython 2010: Registration, Call for Papers and Call for Volunteers

emoticon:ir_scope Planning for EuroPython 2010 is well underway. Both registration (Early Bird) and the talk submission system are open, and we need your help!

As well as the core conference itself there are tutorials before the conference, development sprints afterwards and Python Language Summit and a Python Software Foundation meeting (first one in Europe) during the conference. Confirmed speakers include Brett Cannon and Raymond Hettinger. We also have some exciting keynote speakers that have yet to be announced.

There is lots to do and we need your help! Ways you can help:

  • Come to the conference! Seriously, register now.
  • Join the EuroPython Improve Mailing List and introduce yourself
  • Submit your talk, tutorial and sprint proposals
  • If your company uses Python then they need to sponsor EuroPython. Hassle your boss today
  • Publicise EuroPython in your community

EuroPython 2010 - 17th to 24th July 2010

EuroPython is a conference for the Python programming language community, including the Django, Zope and Plone communities. It is aimed at everyone in the Python community, of all skill levels, both users and programmers.

Last year's conference was the largest open source conference in the UK and one of the largest community organised software conferences in Europe.

This year EuroPython will be held from the 17th to 24th July in Birmingham, UK. It will include over 100 talks, tutorials, sprints and social events.

Registration

Registration is open now at: http://www.europython.eu/registration/

For the best registration rates, book as soon as you can! Early Bird rate will apply until 10th May.

Talks, Activities and Events

Do you have something you wish to present at EuroPython? You want to give a talk, run a tutorial or sprint?

Help Us Out

EuroPython is run by volunteers, like you! We could use a hand, and any contribution is welcome.

Sponsors

Sponsoring EuroPython is a unique opportunity to affiliate with this prestigious conference and to reach a large number of Python users from computing professionals to academics, from entrepreneurs to motivated and well-educated job seekers.

Spread the Word

We are a community-run not-for-profit conference. Please help to spread the word by distributing this announcement to colleagues, project mailing lists, friends, your blog, Web site, and through your social networking connections. Take a look at our publicity resources:

General Information

For more information about the conference, please visit the official site: http://www.europython.eu/

Looking forward to see you!
The EuroPython Team

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2010-03-16 20:17:58 | |

Categories: , Tags: ,


AMD64 or X86-64?

emoticon:podcast Up until recently the 64bit Windows binaries of Python were labelled as being for "AMD64" processors. If you know the history of this architecture then you know exactly what this means, but at least one person was confused and emailed in to ask:

Should I use the AMD64 version of Python on an Intel 64 chip? I know those 64-bit implementations are very similar, but are they similar enough that your AMD64 will work on Intel?

Christian Heimes offered this reply, and suggested an update to the download pages:

The installer works on all AMD64 compatible Intel CPUs.

As you most likely know all modern Intel 64bit CPUs are based on AMD's x86-64 design. Only the Itanium family is based on the other Intel 64bit design: IA-64. The name AMD64 was chosen because most Linux and BSD distributions call it so. The name 'AMD64' has caused confusion in the past because users thought that the installer works only on AMD CPUs.

How about:

  • Python 2.6.4 Windows X86-64 installer (Windows AMD64 / Intel 64 / X86-64 binary -- does not include source)

Martin Loewis (one of senior core-Python developers with a particular responsibility for the Windows releases) objected to the use of the term "X86-64" to describe this architecture:

AMD doesn't want us to use the term x86-64 anymore, but wants us to use AMD64 instead. I think we should comply - they invented the architecture, so they have the right to give it a name. Neither Microsoft nor Intel have such a right.

As a member of the Python.org webmaster team I was concerned that the descriptions were as useful as possible, and am not particularly interested in AMD vs Intel politics. I did a bit of digging to see if X86-64 was now a sufficiently generic term for the AMD64 architecture. Here's what I came up with, none of them individually conclusive perhaps, but indicators of how people understand and use the term:

In conclusion, referring to the AMD64 build as x86-64, with a footnote explaining which architectures this specifically means is unlikely to confuse people. It is definitely better than just saying AMD64.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2010-03-16 19:24:26 | |

Categories: , Tags: , , ,


unittest2 0.2.0 Released and News on nose

emoticon:men I've pushed out a new release of unittest2, version 0.2.0.

unittest2 is a backport of all the fancy new features added to the unittest testing framework in Python 2.7. It is tested to run on Python 2.4 - 2.6.

The biggest new feature in the 0.2.0 release is the addition of shared fixtures; setUpClass, setUpModule, tearDownClass and tearDownModule.

Before I explain these new features there is some interesting news about the next version of nose. Jason Pellerin, creator and maintainer of nose, sent an email to the Testing in Python mailing list. unittest2 and the future of nose:

nose has always been intended to be an extension of unittest -- one that opens unittest up, makes it easier to use, and easier to adapt to the needs of your crazy package or environment.

Fact: unittest2 is coming: http://www.voidspace.org.uk/python/articles/unittest2.shtml

... indeed, it's already here (just not yet in stdlib). It does test discovery (though not by default), can support test functions (though not by default), will eventually support a better form of parameterized tests than nose does, and also will eventually support at least class and module-level fixtures.

Second fact: I have very little time to work on nose anymore, and with offspring #2 due any day now, that's not going to change much for at least a few years.

Third fact: setuptools is dying, distribute is going with it, and something called distutils2 is going to rise to take its place. This matters because a LOT of nose's internal complexity is there to support one thing: 'python setup.py test'. If that command goes away or changes substantially, nose will have to change with it.

Fourth fact: many high-level nose users want it to be released under a more liberal license than LGPL and won't become contributors until/unless that happens.

I think this all adds up to starting a new project. Call it nose2. Call it @testinggoat. Whatever it's called, it should be based on extending unittest2, and designed to work with distutils2's test command. It should support as much of the current nose plugin api as is reasonable given the changes in unittest2. And it should be BSD or MIT licensed (or some reasonable equivalent) so more people feel comfortable contributing.

Actually Jason is mistaken about a couple of the details, unittest2 is just a backport of what is already in the Python standard library and test discovery is supported right out of the box (not quite sure what he means by 'default'). That aside, nose has inspired a lot of the improvements that are in unittest2, and hose still has plenty of features that have not yet made it across. There are some features from nose that will never make it into core unittest, but the foundation of unittest2 and distutils2 will make a new version of nose substantially simpler.

Various people have volunteered to work on the shiny new nose (bite my shiny metal nose?), but the first step is for unittest to gain a decent extension API. After Python 2.7 hits beta this is my highest priority for unittest. Follow the rest of the nose2 email thread for an idea of how it all fits together.

Class and Module Level Fixtures

unittest2 now allows you to create class and module level fixtures; these are versions of setUp and tearDown that are run once per class or module.

Class and module level fixtures are implemented in TestSuite. When the test suite encounters a test from a new class then tearDownClass from the previous class (if there is one) is called, followed by setUpClass from the new class.

Similarly if a test is from a different module from the previous test then tearDownModule from the previous module is run, followed by setUpModule from the new module.

After all the tests in the suite have run the final tearDownClass and tearDownModule are run.

Caution!

Note that shared fixtures do not play well with features like test parallelization and they also break test isolation. They should be used with care.

setUpClass and tearDownClass

These must be implemented as class methods.

import unittest2

class Test(unittest2.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._connection = createExpensiveConnectionObject()

    @classmethod
    def tearDownClass(cls):
        cls._connection.destroy()

If you want the setUpClass and tearDownClass on base classes called then you must call up to them yourself. The implementations in TestCase are empty.

If an exception is raised during a setUpClass then the tests in the class are not run and the tearDownClass is not run. Skipped classes will not have setUpClass or tearDownClass run.

setUpModule and tearDownModule

These should be implemented as functions.

def setUpModule():
    createConnection()

def tearDownModule():
    closeConnection()

If an exception is raised in a setUpModule then none of the tests in the module will be run and the tearDownModule will not be run.

There are a few important details about these functions, particularly how they behave when test suites are randomized. More information in the docs.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2010-03-16 00:09:45 | |

Categories: , Tags: , , ,


Hosted by Webfaction

Counter...