Xceed just released version 1 of their DataGrid. We're super excited here, as it would mean not having to invest in writting our own for the functionality we need. We'll have to see if it's flexible enough to do what we want. Stay tuned!
Friday, 26 January 2007
Thursday, 25 January 2007
Attached Events By Example - Adding an Activate event to any Selector element (ListView, ListBox, TreeView, etc...)
I couldn't resist using a long title. I've had this article on the back of my mind for a while but only managed to get in the mood for a big writing session tonight, after a glass of Stella and a soapy bath. Go figure.
A common struggle with ListViews, TreeViews and other controls is the lack of an ItemActivate event like the one found in WinForms. Several solutions have been offered, most of them involving inheriting from these controls to add the missing functionality. In the spirit of Windows Presentation Foundation's (oh, Avalon, where art thou...) emphasis on composition, I thought I'd offer a generalized solution for any Selector control, with no need for inheritance, using a nearly unknown little gem called attached events.
Attached events are not much talked about (Nick mentions them in passing), documented (msdn mentions them in one paragraph), or printed about (as far as I can read, neither the Petzold nor Chris & Ian's book talk about them, although Ian mentioned to me it will be in the next version). So what is an attached event, and what is not? Let's start with what it is not.
[Disclaimer: At core, attached events are just routed events used in a different way, and as such are more a pattern than an actual specific piece of technology. And as any pattern, they end up with a fancy name. Naming conventions are based on current msdn documentation, which may (and should) change in the future.]
Qualified Event Names
Often, when people think they use attached events, they in fact use Qualified Event Names. This special syntax lets you attach an event handler for a RoutedEvent anywhere in the tree above the element triggering that event. For example, the following code attaches the Click event triggered by the Button element type, but on its direct parent.
1 <Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
2 <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
3 <Button Name="YesButton" Width="Auto" >Yes</Button>
4 <Button Name="NoButton" Width="Auto" >No</Button>
5 <Button Name="CancelButton" Width="Auto" >Cancel</Button>
6 </StackPanel>
7 </Border>
This syntax is the same you'll use for attached events, so it could be said that to add a listener to an attached event, you use the Qualified Event Name notation.
Attached Events
So what are attached events? Let's see what our friends at msdn have to say.
An attached event allows you to attach a handler for a particular event to some child element rather than to the parent that actually defines the event, even though neither the object potentially raising the event nor the destination handling instance define or otherwise "own" that event in their namespace.
If you're like me, it takes a few readings to understand what it means. Let's take it one bit at a time. The first thing to realize is that we're talking about a normal routed event, declared the same way as usual.
public static readonly RoutedEvent ItemActivateEvent =
EventManager.RegisterRoutedEvent("ItemActivate",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ItemActivation));
Then, it is said attached events are defined on an element. Actually they don't need to be defined on an element that is in your element tree at all, and not even on an element. For example, the Mouse class is a sealed class, and yet it defines all mouse-related attached events. What would be more accurate would be to say that the class defining the event is often neither the user of the event (whoever consumes it by adding a handler on it) nor the source of the event (whatever code raises it). For example, the previous attached event has been defined on my ItemActivation class.
namespace SerialSeb.Windows.Controls
{
public static class ItemActivation
{
...
What is common to all attached events however, is the absence of an event declaration using the add{} and remove{} accessors to call AddHandler and RemoveHandler on the instance of the object. Instead, and I suppose it is by convention, two static methods are defined to achieve the adding and removing of an handler.
public static void AddItemActivateHandler(DependencyObject o, RoutedEventHandler handler)
{
((UIElement)o).AddHandler(ItemActivation.ItemActivateEvent, handler);
}
public static void RemoveItemActivateHandler(DependencyObject o, RoutedEventHandler handler)
{
((UIElement)o).RemoveHandler(ItemActivation.ItemActivateEvent, handler);
}
You'll find that all attached events follow the same AddEventNameHandler and RemoveEventNameHandler convention.
The pieces of the puzzle start falling into place slowly. Of course, now that an event has been defined, you want to consume it. To do so, you want to add a handler for that event, but it's not defined on an element present in your tree. The following XAML code shows how it can be done. Note that you can of course add and remove handlers programmatically through the two methods you've defined.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:my="clr-namespace:SerialSeb.Windows.Controls">
<Grid my:ItemActivation.ItemActivate="HandleItemActivate">
...
</Grid>
</Window>
I believe it strikes a phenomenal resemblance to the Qualified Event Name notation I mentioned earlier. Now, whenever the routed event ItemActivate we've defined earlier bubbles up from somewhere within the grid, it will be caught just like any normal routed event. After all, as I said before, this is just a normal routed event.
We've seen how you declare an attached event, and how to add a handler for it anywhere on our tree. The big question is now to know who is going to raise it? Like any routed event, any code can raise this event, as long as it knows on which element it wants to start bubbling it.
Raising an Attached Event
Raising our ItemActivate event needs to be done whenever, within a Selector, a user double-click on an item, or presses the Enter key after selecting one. To do so, we need to attach some behavior to that element, and we're going to use Dan Crevier's excellent attached property trick.
Whenever you declare an attached property, you can define a PropertyChangedCallback that will get called whenever the value of the property changes, and that includes the first time it's applied. And you get a reference to the element it's applied to, absolutely perfect to hook our behavior code to the Selector element!
Let's start by defining the attached property in our ItemActivation class.
public static ActivationMode GetActivationMode(DependencyObject obj)
{
return (ActivationMode)obj.GetValue(ActivationModeProperty);
}
public static void SetActivationMode(DependencyObject obj, ActivationMode value)
{
obj.SetValue(ActivationModeProperty, value);
}
public static readonly DependencyProperty ActivationModeProperty =
DependencyProperty.RegisterAttached("ActivationMode",
typeof(ActivationMode),
typeof(ItemActivation),
new FrameworkPropertyMetadata(ActivationMode.None,
ItemActivation.HandleActivationModeChanged));
Overall a very simple attached property called ActivationMode. The type is a simple enumeration defining what can trigger the raising of our event, Mouse, Keyboard or both. And finally, a call to our HandleActivationModeChanged static method that will provide for the subscription to the events we're interested in.
private static MouseButtonEventHandler SelectorMouseDoubleClickHandler = new MouseButtonEventHandler(ItemActivation.HandleSelectorMouseDoubleClick);
private static KeyEventHandler SelectorKeyDownHandler = new KeyEventHandler(ItemActivation.HandleSelectorKeyDown);
private static void HandleActivationModeChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Selector selector = target as Selector;
if (target == null) // if trying to attach to something else than a Selector, just ignore
return;
ActivationMode newActivation = (ActivationMode)e.NewValue;
if ((newActivation & ActivationMode.Mouse) == ActivationMode.Mouse)
{
selector.MouseDoubleClick += SelectorMouseDoubleClickHandler;
}
if ((newActivation & ActivationMode.Keyboard) == ActivationMode.Keyboard)
{
selector.KeyDown += SelectorKeyDownHandler;
}
else
{
selector.KeyDown -= SelectorKeyDownHandler;
selector.MouseDoubleClick -= SelectorMouseDoubleClickHandler;
}
}
I defined two handlers for the KeyDown and the MouseDoubleClick events of our Selector. Only thing left to do is to raise our event when the user double-clicked on something.
static void HandleSelectorMouseDoubleClick(object o, MouseButtonEventArgs e)
{
ItemsControl sender = o as ItemsControl;
DependencyObject originalSender = e.OriginalSource as DependencyObject;
if (sender == null || originalSender == null) return;
DependencyObject container = ItemsControl.ContainerFromElement(sender as ItemsControl, e.OriginalSource as DependencyObject);
// just in case, check if the double click doesn't come from somewhere else than something in a container
if (container == null || container == DependencyProperty.UnsetValue) return;
// found a container, now find the item.
object activatedItem = sender.ItemContainerGenerator.ItemFromContainer(container);
if (activatedItem != null && activatedItem != DependencyProperty.UnsetValue)
sender.RaiseEvent(new ItemActivateEventArgs(ItemActivation.ItemActivateEvent, sender, activatedItem, ActivationMode.Mouse));
}
We get the container (the one returned by the ItemsControl.ItemTemplate property), from which we can get the Item that is being represented by this fragment of the tree.
And there you have it, we raise our attached event by calling the RaiseEvent method of our target element! Our attached event is now going to bubble from the Selector and you'll be able to catch it wherever you want in your tree.
Conclusion
We now have an ItemActivate routed event that can be used on any selector, by using attached properties and attached events. Through composition we've added behavior and functionality to a type without having to inherit from it.
Next time, we'll learn how to bind a RoutedEvent to a Command anywhere in your XAML. Stay tuned...
P.S. I'll post the complete code, with keyboard support, and tunneling PreviewItemActivate event when I find the time to set-up my other web sites. In the meantime, don't hesitate to copy and paste!
Wednesday, 24 January 2007
You know how it is sometimes...
.. you download stuff and you forget to even install it. I did that today with StyleSnooper. Added to my toolbox!
Thursday, 18 January 2007
Going through my WPF backblog...
I use this post as a bookmark as much for myself as for others.
Mike Hillberg talks about the Loaded and Initialized event, as well as about Trace sources in WPF.
How to show different text based on an enum value?
This question was asked on the MSDN forums, so I thought I'd replciate it here for everybody's benefit.
When you want to bind to a value that's an enumeration, sometimes you want to show specific text. While writting a converter is one way of solving this problem, it requires writting code. And no code is of better quality than the one you don't write.
So here's the sample I posted.
MainWindow.xaml:
1 <Window x:Class="MsdnForums.TextBlockBoundToEnum.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:local="clr-namespace:MsdnForums.TextBlockBoundToEnum"
5 Title="TextBlockBoundToEnum" Height="300" Width="300"
6 >
7 <!-- Forums: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1107413&SiteID=1 -->
8 <DockPanel>
9 <StackPanel DockPanel.Dock="Right">
10 <TextBlock>Current CurState value:</TextBlock>
11 <Button Click="HandleGoodButtonClick">Good</Button>
12 <Button Click="HandleBadButtonClick">Bad</Button>
13 <Button Click="HandleUglyButtonClick">Ugly</Button>
14 </StackPanel>
15 <TextBlock>
16 <TextBlock.Style>
17 <Style TargetType="{x:Type TextBlock}">
18 <Style.Triggers>
19 <DataTrigger Binding="{Binding Path=CurState}" Value="Good">
20 <Setter Property="TextBlock.Text" Value="That was a good one!" />
21 </DataTrigger>
22 <DataTrigger Binding="{Binding Path=CurState}" Value="Bad">
23 <Setter Property="TextBlock.Text" Value="That's no good!" />
24 </DataTrigger>
25 <DataTrigger Binding="{Binding Path=CurState}" Value="Ugly">
26 <Setter Property="TextBlock.Text" Value="Ooooooooooh look at hiiiim!" />
27 </DataTrigger>
28 </Style.Triggers>
29 </Style>
30 </TextBlock.Style>
31 </TextBlock>
32 </DockPanel>
33 </Window>
MainWindow.cs
1 using System;
2 using System.Windows;
3
4 namespace MsdnForums.TextBlockBoundToEnum
5 {
6 public partial class MainWindow : System.Windows.Window
7 {
8 private MyDataContext myDataContext;
9 public MainWindow()
10 {
11 InitializeComponent();
12 this.DataContext = myDataContext = new MyDataContext();
13 }
14 public void HandleGoodButtonClick(object source, RoutedEventArgs e)
15 {
16 myDataContext.CurState = APP_STATE.Good;
17 }
18 public void HandleBadButtonClick(object source, RoutedEventArgs e)
19 {
20 myDataContext.CurState = APP_STATE.Bad;
21 }
22 public void HandleUglyButtonClick(object source, RoutedEventArgs e)
23 {
24 myDataContext.CurState = APP_STATE.Ugly;
25 }
26 }
27 public class MyDataContext : DependencyObject
28 {
29 public APP_STATE CurState
30 {
31 get { return (APP_STATE)GetValue(CurStateProperty); }
32 set { SetValue(CurStateProperty, value); }
33 }
34 public static readonly DependencyProperty CurStateProperty =
35 DependencyProperty.Register("CurState", typeof(APP_STATE), typeof(MyDataContext), new UIPropertyMetadata(APP_STATE.Good));
36 }
37 public enum APP_STATE
38 {
39 Good,
40 Bad,
41 Ugly
42 }
43 }
To profile your WPF applications
Tim Cahill talks about profiling your WPF application. Not a new post but one worth knowing, bookmarking and using!
Monday, 15 January 2007
Blog or forum...
I've not been very active updating this blog. First, I'm in the process of setting up another blog that would support some stuff I have pending, and on my own domain name. Plus it would be nice to re-upload my old archives of entries and without controlling the code it's gonna be hard.
Second issue for me, and the major one, is that my time is extremely limited as we're all working very hard on the project we're working on at the moment (WPF of course). And it comes down to a choice to make between my blog and my contributions in the msdn forum.
So for now, you can find me on the msdn wpf forum. I genuinely think my time is better spent there for the next few weeks, until the new blog arrives!
Wednesday, 3 January 2007
Using winmerge as a merge tool in Visual Studio Team Suite
Found in msdn, to change the diff/merge tool used on conflicts with TFS:
Go to Tools > Options > Source Control > Visual Studio Team Foundation Server > Configure User Tools...
Add a Compare pointing to winmergeU.exe and using:
/e /x /s /wl /dl %6 /dr %7 %1 %2
as a command-line argument.
Repeat the operation for merge, this time using:
/e /s /x /ub /dl %6 /dr %7 %1 %2 %4
Enjoy!
Thursday, 21 December 2006
Thought of the day
Or the night rather...
[Conditional("GRIDPARANOIA")]
Yeah, I'd be suffering from a paranoid condition if I had responsability to code the System.Windows.Controls.Grid :)
Wednesday, 20 December 2006
DependencyProperty inheritance doesn't cross the UserControl boundary!
Still got lot of work to do tonight but more on that tomorrow.
Friday, 15 December 2006
Visual Studio 2005 SP1 is released!
From Bharry's blog:
Finally. Pure bliss. Orgasmic patching friday.
Thursday, 14 December 2006
Thought of the day
Music to buy. That's an interesting concept, and more and more people start buying their music rather than just copying their friend's iPod lists (or worse, swap 80GB hard drives like some do). And that's fine with me. I'll be honest and admit I listen to Internet radios and often download whichever trendy songs I want from my friends iPods. Very bad boy...
Why not buy then? Working in I.T. as a contractor, I cannot give the argument of being poor. The reason is simple, quality.
See, after a while, when you start having nice hardware that try and reproduce digital audio signal in something of rather good quality, you start feeling the pain of the general poor quality of encodings. Be it on limewire networks or on the songs you buy online, it is compressed, and I cannot stand the general sound of it. It lacks amplitude and is often metallic.
Plus, I feel it is absolutely stupid to impose DRM on me when the CD equivalent doesn't have any protection, and is of better quality because unencoded.
The day a shop will start giving me uncompressed music with no DRM (meaning I can have it running on my MacBook pro, my vista machine at home, my iPod, my XBOX360), then I'll start paying for the service.
In the meantime, CD is still the best and the king of quality. They're securely backed up on my RAID array at home, and I have a copy in VBR for my iPod. I just don't know what to do with the original hardware sadly.
Maybe a CD-themed Christmas tree this year?
Wednesday, 13 December 2006
TFS and offline mode
Ah the joy of using TFS. Don't get me wrong, the server looks like a well architected and well thought source server. No, my real problem is the client.
First, I hate Visual Studio trying to manage my source control. I hated it when SourceSafe was around, hated it with SourceGear Vault, and was very happy no real solution existed to get some manager to decide it was a good idea when I was using Perforce, cvs or svn.
Second, why in the name of god should I check-out a file to work on it? It has no rationale whatsoever. My colleagues ask me to check-in files I've checked-out, but what for? People end up being anal about a check-out that has no value whatsoever. I believe I should be in control with no pressure from anyone to check-in my file after I merge it with whatever changes have been made since the last time I got the last version. And I also believe it's my responsibility and my responsibility alone to ensure I merge my code. Don't take away my freedom!
Third, how could they forget most developers don't have access to their source server all the time? Maybe it's an independent developer thing, but in the evening I like modifying code here and there... And I can't! Because each modify end up in visual studio asking me to check-out the file... Argh!
Not all is lost though. It would seem that some people at Microsoft thought of us when they released the Team Foundation PowerToys. Once you've installed it and added it to your path, you can do the lovely, and oh so sweet:
C:\mysrc> tfpt online
And off you go. This little gem will compare all your files and let you know which ones are different from the server, letting you queue the changes for the next time you reconnect to the server. Of course the attrib -r is your responsibility, but it's a small price to pay.
Also very useful for people that wants to know which files they've actually modified when they check-out a whole solution or project before going home, the uu command works the same way as the Cancel check-out for unmodified files (or similar name) that the perforce client has. Sweet.
Now when will we be able to remove this check-out absurdity all together and go back to the more natural modify - get latest - merge - commit ? Oh and a powershell version of the tool would be fabulous...
Tuesday, 12 December 2006
Building a Vista Development machine
As I said yesterday, once most of the driver issues are resolved, it's time to install everything else. Gigs of it. So here's a list. I'll update as I remember all the stuff I've not needed yet.
[Update: After a whole day of installation, the machine is still not ready. I'll add some more of my tools tonight. Ah if only there was one way to click with a nice checkbox on the software you want to get installed on a machine and let it do its job on its own. A bit like a package manager...]
- Windows Live Writer beta √
- Box.net add-in
- Source code formatter add-in
- Windows Live Messenger 8.1 beta (the 8.0 seems very unstable under Vista) √
- Windows Live Mail Desktop beta √
- Visual Studio Team Suite (I'll talk about my hate relationship with tfs a bit later. Get latest - merge - commit: good. Check out - readonly files crap - try and checkin - no offline mode, pleeeeeeeeeeease). √
- Windows SDK (.net framework 3 sdk really) √
- SQL Express 2005 SP2 CTP √
- SQL Management Studio Express SP2 √
Visual Studio Web Application Project- Visual Studio 2005 SP1 beta √
- Team Foundation Client √
- .net 3.0 Visual Studio 2005 Extensions √
- XML Notepad 2007 √
- Expression Blend beta √
- Snoop √
- GhostDoc 2005√
- TestDriven.net 2.0 √
- Reflector √
- PowerShell 1.0 RC2 X No support for vista RTM...
- WiX
- Snippy √
- VSI Content Installer Powertoys
- CLR Profiler
- Process Monitor √
- Process Explorer √
- PSTools
- DebugView
- Xpath Mania √
- Gmail Notifier
- Skype √
- Adobe Acrobat √
- DPack Visual Studio 2005 Add-ins
- WinMerge (and how to replace the merge tool in TFS)
- CopySourceAsHtml
MacBook Pro Core2 Duo 17in just arrived!
Ah the pleasure of receiving new hardware.
First and foremost, what a fantastic packaging. You can already feel this geeky chic pleasure just from the box. Everything fits in nicely, no bulky documentation, no stupid 4mx3m posters explaining how to press the power key. Pure pleasure.
The form factor is fabulous. Most of my co-workers looked at it, commenting on the form factor, the sleekness, the weight... Some looked at it like a strange animal they've never seen before.
And yet it's one of the best performing Intel laptop you can find on the market. The equivalent Dell XPS came to a bit more than £100 more (that said, factor out the cost of a bit higher resolution screen and a windowsXP license... But factor in the webcam, the FireWire...)
So the bootcamp software installed like a breeze, followed by a reboot. My advice, kill the partition that was created and create a fresh one (remember, it's partition 3). You do gain back that 100 megs that were left over by bootcamp partitioning.
Next step is to install the drivers. Do not try to run the installer, it fails miserably, and apple failed to provide an installer that rolls back properly. When they mean windowsXP SP2 they really mean it.
To get your drivers, simply call the .exe on the mac driver from the command line, with the arguments /a /V (notice the capital v). That will expand your drivers in the location you provide. Install all the drivers by going to your Hardware and devices snap-in, but don't try either the video or the keyboard ones [update] or the audio ones...[/update] For the former you simply want to reach ATI's web site and download the latest ATI Mobility X1600.
For your audio drivers, you want the vista version of the Intel drivers available here.
For the trackpad support, you need to manually install the driver by replacing the HID-compliant mouse from the Devices MMC snap-in. This should give you right click and scroll support.
The iSight driver didn't work on the first installation for me so i had to reinstall it, and it's now perfect.
Bluetooth is a bit more complicated. Follow these instructions.
For the keyboard, Apple missed the boat completely. Not only do they not remap all the keys, but they manage to confuse the trema (¨) and the double quote ("). Unacceptable. But then again, the keyboard driver author must've not been a developer.
Instead of relying on apple, go over to Damiens' weblog and download the macBook Pro keymaps. The link to Input remapper is also very useful so highly recommended.
And if you need it, bookmark the link. I didn't and just spent a two whole hours to find this link again, just to bring it to this blog!
Next, tomorrow, a list of all the nice pieces of software I'll install on my machine over the day to turn it into a nice killer dev box!
Monday, 11 December 2006
WPF Snippets
VS Extensions code named Cider, that will stop shipping for Visual Studio 2005 after the November CTP (which means there won't be any more support as far as I understand unless you download the CTPs of the next Visual Studio environment code name Orcas), install a few WPF snippets.
Frankly, the C# snippets are sub-optimal. So I rewrote them.
- propdp and propa are still there, I just removed the comments, put the containing class as the owner of the event and replaced the default value as default(type).
- propdpg for read-only dependency properties.
- revent for a default routed event (with eventNameEvent for the static backing store and eventName for the CLR event wrapper.
- reventt for a tunneling event, with the Preview naming convention.