Friday, September 11, 2009

Struggling with basic behaviour

Ran into some problems again :-(.
1) If I populate the listbox with lot's of items, let's say 200, it falls of the screen!
In winforms I would expect a scrollbar to appear, but not in my WPF app :-(. So I added a Scrollviewer around the listbox, but to no avail.
After looking around on the net, it appears that the stackpanel is at fault, my Listbox resides in a StackPanel, and this combination makes the items of the listbox to go beyond the window edge.
Solution : remove the stackpanel, this was not realy needed (yet) for my program, and when I do need a container control in the future, I'll use a grid

2) Standard a listbox does not support double-click
Surfs up : listbox double click. You have to manually add the event.

3) When I use the ItemContainerStyle in stead of the ItemTemplate, like said in the previous post, the listbox looses the ability to select items somehow. Go figure. If there is a soul out there, that can explain me the difference between ItemContainerStyle and ItemTemplate, please enlighten me.

Now these are the kind of things that drive me nuts when I am programming in WPF !
When coming from Windows Forms, I keep on stumbling on these basic stuff that is natively in windows forms, but not in WPF, I think I'll join the WPF Hate team, and stick to console and winforms. At least those work, no eye candy, but I can say it'll be finished in 2 days.

I mean it is easy to create Star Wars like stuff in WPF, but real working apps, that remains to be seen. Ok, there are a lot of showcases to be found on the Net, but I wonder how many devs and designers where put on those projects. Also read this page

laying off for a while, getting too angry ...

Thursday, September 10, 2009

Keeping the overview

The main window is getting into shape, but I do not like the Xaml code :-(. It's getting big, and I'm loosing the overview. Below is a small example of what I mean, the window just shows the CCNet project data, (name, description, ...) and a list of the issues (key, description, votes, ..)
<Window x:Class="JiraWpf.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="clr-namespace:JiraWpf"
Title="Window2" >
<Window.Resources>
<ObjectDataProvider x:Key="CCNetProject"
ObjectType="{x:Type lc:CCNetJiraDataProvider}"
MethodName="GetCCNetProject"
/>
<ObjectDataProvider x:Key="AllProjects"
ObjectType="{x:Type lc:CCNetJiraDataProvider}"
MethodName="GetProjects"
/>
<ObjectDataProvider x:Key="AllIssues"
ObjectType="{x:Type lc:CCNetJiraDataProvider}"
MethodName="GetIssuesFromFilter"
/>
<DataTemplate x:Key="IssueTemplate" >
<StackPanel Orientation="Horizontal" >
<Label Content="{Binding Key}" Width="200"/>
<Label Content="{Binding Description}" />
<Label Content="{Binding Votes}" />
<Label Content="{Binding Summary}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<DockPanel>
<Expander VerticalContentAlignment="Center" DockPanel.Dock="Left" DataContext="{StaticResource CCNetProject}" >
<StackPanel Orientation="Vertical" >
<Label Content="{Binding Lead}" />
<Label Content="{Binding Key}" />
<Label Content="{Binding Description}" />
<Label Content="{Binding Name}" />
</StackPanel>
</Expander>
<ListBox DockPanel.Dock="Top"
ItemsSource="{Binding Source={StaticResource AllIssues}}"
ItemTemplate="{StaticResource IssueTemplate}"
/>
</DockPanel>
</StackPanel>
</Window>
As you can see, that is getting big, and it shows almost nothing :-( Time to clean house!
First thing I do not like is the difference of showing data between the listbox and the expander. I prefer the one of the listbox, where you say that the items have the specified template, leaving the actual layout out of the overview.
Downside is that Expander does not have an ItemTemplate property, and creating that property and functionality on all kind of controls does not sound as an attractive solution. The good news is that does exist already, WPF has this functionality build in : you just have to use basic controls and styles !
Now that is what I like : basic controls. Never thought that I would ever say : I like something about WPF ;-)
So I created a style that just sets the Control.Template property, this allows to easily create a visualisation of a certain item, the way like the it is done with a DataTemplate. And this style is applied to a ContentControl residing in the expander. This is already better, but now I have a style and a datatemplate to visualise data, and personally I do not like 2 ways of doing the same stuff in 1 program. So I changed the DataTemplate also into a style. This means that I have to use the ItemContainerStyle property in stead of the ItemTemplate in the listbox.
That being done, the way of presentation is more consistent, but the file is still rather big. It would be nice if the resources where moved into a separate file, or set of files, and luckily this is also supported out of the box with the ResourceDictionary.MergedDictionaries. Resulting in the following for the window.xaml
<Window x:Class="JiraWpf.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="clr-namespace:JiraWpf"
Title="Window1" >
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/DataProvider.xaml" />
<ResourceDictionary Source="Resources/DataLayout.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<DockPanel>
<Expander VerticalContentAlignment="Center" DockPanel.Dock="Left" DataContext="{StaticResource CCNetProject}" >
<ContentControl Style="{StaticResource CCNetProjectTemplate}" />
</Expander>

<ListBox DockPanel.Dock="Top"
ItemsSource="{Binding Source={StaticResource AllIssues}}"
ItemContainerStyle="{StaticResource IssueTemplate}"
/>
</DockPanel>
</StackPanel>
</Window>

As you can see, a lot shorter and more easily to follow. Below is the DataLayout.xaml file, I did not post the DataProvider.xaml file, because it is just a copy and paste of all the ObjectDataProviders into an empty ResourceDictionary file.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="CCNetProjectTemplate" >
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Vertical" >
<Label Content="{Binding Lead}" />
<Label Content="{Binding Key}" />
<Label Content="{Binding Description}" />
<Label Content="{Binding Name}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style x:Key="IssueTemplate" >
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal" >
<Label Content="{Binding Key}" Width="200"/>
<Label Content="{Binding Description}" />
<Label Content="{Binding Votes}" />
<Label Content="{Binding Summary}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>


Now this looks like it is more manageable. Stay tuned ...

Monday, September 7, 2009

Binding data with INotifyPropertyChanged

Wpf can only bind to properties, and in order to let it automatically refresh the UI when a bound data item changes value, it must implement the INotifyPropertyChanged property.
This means that the following code does not suffice :
using System;
namespace JiraWpf.DataObjects
{
public class Person
{
public Person()
{
}
public string Name { get; set; }
}
}

So let's implement the interface, this leads us to the following :
using System;
using System.ComponentModel;

namespace JiraWpf.DataObjects
{
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public Person()
{ }

private string name;
public string Name
{
get { return name; }
set
{
if (value != this.name)
{
this.name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
}

private void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, args);
}
}
}

Not a big change, but if you have a lot of properties, this is very bad. We came from a 1 line property implementation to a 13 line !! The lines for the event and raising it are common for all properties, so they are not counted. Every property compares its value with the previous value, and when those 2 are different it raise the change-event. This is functionality that is the same for all properties so it belongs somewhere else. It would be handy if an attribute NotifyOnChange could be set on a property, but this does not exist (as far as I know). Meaning this comparison must be done in a self made base class : NotifyingObject, leading us to the following :
using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace JiraWpf.DataObjects
{
public class NotifyingObject : INotifyPropertyChanged
{
private Dictionary<string, IComparable> props;

public event PropertyChangedEventHandler PropertyChanged;

public NotifyingObject()
{
props = new Dictionary<string, IComparable>();
}

public void SetValue<T>(string name, T value) where T : IComparable
{
if (!props.ContainsKey(name))
{
props.Add(name, value);
}
else
{
if (props[name].CompareTo(value) != 0)
{
props[name] = value;
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
}

public T GetValue<T>(string name) where T : IComparable
{
return (T)props[name];
}

protected void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, args);
}
}
}
using System;
using System.ComponentModel;

namespace JiraWpf.DataObjects
{
public class Person : NotifyingObject
{
public Person()
{ }

public string Name
{
get { return base.GetValue<string>("Name"); }
set { base.SetValue("Name", value); }
}
}
}

Ok, this is already much better, property definitions are back to just 5 lines, but at least it is readable again. The NotifyingObject has a list of the property names and their values, so on a get, we look up the value in the list. On a set we compare with the value in the list, and when the 2 are different, we raise the event.
Now what still bothers me on this is that we type the property name 3 times in the Person class! Once in the property name itself, secondly in the get and another time in the set. This is asking for trouble on refactoring (renaming the property). No tool will change those names in the get and set, they are hardcoded string values ! This leads us to the following implementation of the 2 classes :
using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace JiraWpf.DataObjects
{
public class NotifyingObject : INotifyPropertyChanged
{
private Dictionary<string, IComparable> props;

public event PropertyChangedEventHandler PropertyChanged;

public NotifyingObject()
{
props = new Dictionary<string, IComparable>();
}

public void SetValue<T>(T value) where T : IComparable
{
string name = GetCallerPropertyName();

if (!props.ContainsKey(name))
{
props.Add(name, value);
}
else
{
if (props[name].CompareTo(value) != 0)
{
props[name] = value;
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
}

public T GetValue<T>() where T : IComparable
{
string name = GetCallerPropertyName();
return (T)props[name];
}

protected void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, args);
}

private string GetCallerPropertyName()
{
System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame currentFrame = stack.GetFrame(2);

//strip of the set_ and get_ prefixes of the generated property names, to get the ones you typed
string propname = currentFrame.GetMethod().Name.Substring(4);

return propname;
}
}
}
using System;
using System.ComponentModel;

namespace JiraWpf.DataObjects
{
public class Person : NotifyingObject
{
public Person()
{ }

public string Name
{
get { return base.GetValue<string>(); }
set { base.SetValue(value); }
}
}
}

We let the NotifyingObject look up the property name itself, preventing us from ever typing in a wrong name. This is now safe for refactoring.
Another benefit of the NotifyingObject is that you could foresee a way to postpone the raise event till all properties are set, a BeginUpdate and EndUpdate function could foresee that. Implementing extra logging is now also much easier. Anyway, with this baseclass ready, I can start working on the next part.
Stay tuned ....

Sunday, September 6, 2009

Wpf : what you really need to understand first is databinding

Learning the basics layout of a WPF program is not that difficult, I mean how to place basic controls like buttons, labels, textboxes, ... Making a great interface is something else, but that requires other skills, UI designer skills. But what I think is the most important part to understand early is the binding. This is the part of the course / book that I advise you to spent the most time on. I've lost too many hours finding out why my data was not visualised :
° Compilation : 0 warnings, 0 errors
° Setting a breakpoint on the method that delivered the data : breakpoint was never hit
° Output window did not show any errors, warnings
° Settings the ItemsSource in code behind works like a charm, so the data providing method works !

Below is an example program, see if you can find the mistake.
<Window x:Class="Damn.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="clr-namespace:Damn"
Title="Window1" >

<Window.Resources>
<ObjectDataProvider x:Key="CCNetProject"
ObjectType="{x:Type lc:CCNetJiraDataProvider}"
MethodName="GetProjects"
/>

<DataTemplate x:Key="CCNetProjectTemplate" >
<StackPanel Orientation="Horizontal" Background="LightBlue">
<Label Content="{Binding Key}" Width="200"/>
<Label Content="{Binding Description}" />
</StackPanel>
</DataTemplate>
</Window.Resources>

<StackPanel DockPanel.Dock="Top" Orientation="Vertical">
<ListBox x:Name="lstY"
ItemsSource="{Binding StaticResource CCNetProject}"
ItemTemplate="{StaticResource CCNetProjectTemplate}"/>
</StackPanel>

</Window>

So this has kept me busy for some hours, ok the mistake was mine, but how could I have found the error more easily?
I can not set a breakpoint in XAML, and setting a breakpoint in the method did not get hit. How do other WPF programmers pinpoint these kind of problems. For me this is a serious problem / shortcoming in Visual Studio. Any body knows some tips/tricks ?

Friday, September 4, 2009

Wpf : learning new grounds

Part of my main job involves WPF, which I find hard to learn, coming from winforms and making mostly console utility apps (no events flying around). So I needed a pet project to learn this. I looked at CCNet, it has no WPF frontend, but that already exists, Cradiator aka BVC2, so another candidate had to be found.

A painpoint for me as developper on CCNet is to find items which have patches, our Jira Issue site has many search options, but that one is not provided. The good point is that there is a wsdl connection available to the issue tracker, which has this possibility ! An issue has an Attachements property, which is a list of strings, the filenames!
Basically all I need to do is get all open items, and filter out the ones where Attachements.Length != 0 .

The console version was finished in half an hour, the WPF, I'm still working on :-(
Details will follow in next posts. I'm also taking this project as a way to improve my knowledge of other items like patterns, refactoring, ...
And I want to do this project the 'WPF way', not winform like.

Do not worry, I'll keep working on CCNet to :-)