dinsdag, februari 07, 2006

Skinable applications in C#

Normale applicaties zijn best ok. Een tekstverwerker moet eruit zien als een tekstverwerker. Een tekstverwerker moet er niet uitzien zoals Winamp of ITunes dan raak je namelijk in de war. Maar als je echt een stoere applicatie maakt, moet hij er natuurlijk ook stoer uit zien. Alleen hoe?

In de afgelopen weken ben ik bezig geweest met het uitvogelen van de WIN32 api. Op mijn werk worden elke nacht automatisch tests gedraaid op de applicatie. Eén van die tests heeft een window en die test gaat alleen verder wanneer dat window weggeklikt wordt. Zie je het probleem? Nightly builds en een window dat weggeklikt moet worden? Na wat gepuzzel kwam ik erachter dat door een WM_CLOSE command te sturen het window zich afsloot, en zo was mijn interresse voor de WIN32 api gewekt.


FastForward naar afgelopen donderdagavond. Samen met Rob, een goede vriend van mij, stond ik me te ergeren in de rij van de Free Record Shop in Den Bosch(Over klantonvriendelijkheid gesproken: 5 man in de rij, en de 2 personeelsleden staan gezellig met elkaar en een bekende van hun te kletsen... Wat een baggertent). Als programmeurs vertellen we elkaar altijd waar we mee bezig zijn. En Rob vertelde mij over een Frame waar hij een eigen titelbalk voor had gemaakt met wat WIN32 api calls.
Mijn interesse was gewerkt en ik ben het uit gaan zoeken(met hulp van Rob ;)

Wat is het probleem: je wilt een stoere applicatie en je wilt dus niet dat Windows een Titlebar tekent. Je wilt er namelijk zelf één tekenen. Een stoere! Echter moet die balk zich wel gedragen als een Titlebar: je moet het window ermee kunnen slepen en bij dubbelklik moet het window zich maximaliseren etc. Dat kun je natuurlijk allemaal zelf inprogrammeren. Maar windows kan dat al. Waarom dat niet hergebruiken? De truc zit hem in het afvangen van Windows Messages en de WIN32 api.

protected override void WndProc(ref Message m)
{

if (m.Msg == 0x84) // WM_NCHITTEST
{
m.Result = new IntPtr(2); // HTCAPTION
}
else
{
base.WndProc(ref m);
}
}

Bovenstaande code geeft aan: als de message van type WM_CHITTEST is, geef dan waarde 2 terug als result. En die 2 geeft aan dat de muis over de CAPTION zit. Deze code in een frame is voldoende om te zorgen dat een heel frame zich als een TitleBar gedraagt. Mooier zou zijn als alleen een bepaalde regio zich zo gedraagt. Daarvoor kun je aan message de muis positie opvragen en afhankelijk daarvan de waarde teruggeven.

Maar wat bleek: als ik een Panel neerzet in het Frame met docking top en daar mee wilde slepen werkte het niet. Het window wilde zich niet verplaatsen. Toen heb ik bovenstaande code ook in het Panel gezet, wat raar gedrag opleverde: ik kon het panel binnen de applicatie verslepen. Leuk maar niet gewenst.



Na wat gezoek kwam ik op de volgende code om te zorgen dat ook het panel zich gedraagde zoals verwacht:


public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg,
int wParam, int lParam);

protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84) // WM_NCHITTEST
{
ReleaseCapture();
SendMessage(Parent.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
else
{
base.WndProc(ref m);
}
}




Ik heb nu een glimp opgevangen van de WIN32 api en ik moet zeggen dat ik verbaasd ben door de mogelijkheden. Ik heb echt zin om hem nog beter te leren kennen!

Edit: IE rendert de blog fout door code tags, blockquotes van gemaakt...

3 opmerkingen:

Anoniem zei

Je kunt met de API ook alle controls etc. zelf tekenen en/of invullen terwijl de functionaliteit hetzelfde blijft.

Check eens de term "ownerdrawn"

Leuk spul!

Rob L.

Hans van Leuken zei

Ja, dat is handig voor .Net controls. Echter is de titlebar van een Window geen .Net control.

Neemt niet weg dat dat handig is. (alleen zou ik het liever doen via overerving)

Anoniem zei

Owner drawn was er al in de tijd van "normale" WIN32 apps. Dus dat moet ook (juist!) werken met de titlebar. De vraag is alleen of je d'r interop voor nodig hebt of niet ... :)

Rob L.