Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

drakon

Supermoderator

  • »drakon« ist der Autor dieses Themas

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

1

18.11.2008, 10:40

Entwicklung von Windows Gadgets

Entwicklung von Windows Gadgets

Inhaltsverzeichnis
[list] 1. Einleitung
2. Alphablending
3. Eine einfache Uhr
4. Referenzen[/list]1. Einleitung
Manch einer fragt sich jetzt, was ein Windows Gadget überhaupt sein soll und für was man das gebrauchen kann.
Populär wurden diese kleinen Programme (bei Windows) vor allem seit Windows Vista, wo man seinen ganzen Desktop mit allerlei nützlichem und weniger nützlichem zupflastern kann. Da gibt es Uhren, kleine Überwachungstools, Notzitzzettel, Taschenrechner, RSS-Feeds, Searchbars und so weiter und so fort.

(Link)


(Link)


Wie man auf den Bildern sehr schön sehen kann, ist der Fantasie keine Grenzen gesetzt.
Mac User kennen diese kleinen Helfer wahrscheinlich schon ein wenig länger und haben sogar einen eigenen Spielplatz, wo sie sich tummeln können. Nennt sich dort Dashboard.
Doch wie bringe ich einen Windows XP-Desktop dazu zu einem gestandenen Dashboard zu mutieren?
Wenn du schon immer einmal dein eigenes Gadget programmieren wolltest und den Markt mit deinen mehr oder weniger nützlichen Programmen überschwemmen wolltest, dann bist du hier richtig und solltest weiterlesen.

2. Alphablending
Der wichtigste Effekt für ein solches Gadget ist Alphablending, da kantige Übergänge einfach nur schreklich aussehen.
Und da kommt auch schon das Problem. Die WinAPI ist kein Freund des Alphablending, somit muss man einen kleinen Umweg nehmen, der aber auch wieder Nachteile mit sich bringt. Das sieht man im Code daran, dass man, wenn man z.B etwas mit einer Font schreiben möchte man zuerst eine DC erstellen muss, dort reinschreiben und dann auf die eigentliche DC per AlphaBlend () zeichnen muss, da ansonsten der Hintergrund nicht mitberechnet wird. ( Darauf gehen ich später noch genauer ein).



3. Eine einfache Uhr

Ich habe als Beispiel eine einfache, kleine Uhr genommen, die lediglich aus einen Hintergrund und der Zeit besteht. Grundsätzlich sind viele Gadgets auch nicht viel mehr, als ein paar schöne Bilder, Schrift, ein paar Effekte und vielleicht eine kleine Animation. Auf das alles werde ich jetzt im Moment noch nicht eingehen (vielleicht gibt es irgendwann einmal ein zweites Tutorial), aber ich denke, dass das relativ leicht auch selbst zu erarbeiten ist, wenn die Grundlagen einmal gelegt sind.

Zum testen einfach dieses Bild als background.png im Projektverzeichnis speichern und Code kompilieren. (ich kenne den Drang zuerst einfach mal zu sehen, ob es überhaupt funktioniert. :))
Auch nicht zu vergessen, dass und GdiPlus.lib und Msimg32.lib noch mitgelinkt werden müssen.

Anscheinend scheint die Professional Version vom MVC++ irgendwie andere Einstellungen zu haben, als die Express Edition. Da ich die Professional Version nicht habe,kann das leider nicht testen. Also, wer es testen will und die Einstellung nicht findet, einfach die Express Edition laden, dann sollte das klappen. ( Falls jemand weiss, welche Einstellungen das sind, bitte posten).

Hintergrund:

(Link)


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <windows.h>
#include <gdiplus.h>

#include <string> 
#include <sstream>
#include <fstream>

bool AppRun = true;
unsigned int width = 120;
unsigned int height = 60;

::LRESULT CALLBACK WndProc (::HWND hwnd, ::UINT message, ::WPARAM wParam, ::LPARAM lParam);
::WNDCLASS WndClass;
::HINSTANCE hInstance;
::HWND hWnd;
::SIZE BackgroundSize = { width , height};
::RECT WindowRect = { 0 , 0 , 0 , 0 };
::POINT SourcePoint = { 0 , 0 };
::SYSTEMTIME SysTime;

Gdiplus::GdiplusStartupInput gdiplusStartupInput;
::ULONG_PTR gdiplusToken;

// Blendfunktion

::BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };

void Write ( 
            ::HDC hdcDest , 
            std::wstring out ,
            unsigned int xPos ,
            unsigned int yPos ,
            unsigned int width,
            unsigned int height
            )
{
    // temporäre DC erstellen

    ::HDC hdcTempFont = ::CreateCompatibleDC (hdcDest);

    // temporäre Bitmap erstellen

    ::HBITMAP hTempFont = ::CreateCompatibleBitmap (hdcDest, width,height);

    // Bitmap auswählen

    ::SelectObject ( hdcTempFont, hTempFont );

    // Font erstellen und auswählen

    ::HFONT hFont = ::CreateFont (0,0,0,0,0,0,0,0,0,0,0,0,0,L"fixedsys" );
    ::SelectObject ( hdcTempFont, hFont );

    // Text Farbe einstellen 

    ::SetTextColor ( hdcTempFont , RGB ( 255,0,0) );

    // Hintergrund für Font einstellen

    ::SetBkMode(hdcTempFont, TRANSPARENT); 

    // in temporäre DC schreiben

    ::TextOut ( hdcTempFont , xPos,yPos,out.c_str(),out.size() );
    
    // von der temporären DC auf die eigentliche DC bliten

    ::AlphaBlend (hdcDest,0,0,width,height,hdcTempFont,0,0,width,height,blendPixelFunction);

    // temporäre DC zerstören

    ::DeleteDC ( hdcTempFont );

    // temporäre Bitmap zerstören

    ::DeleteObject ( hTempFont );
}

int WINAPI WinMain (
                    ::HINSTANCE hInstance, 
                    ::HINSTANCE hPrevInstance,
                    ::PSTR szCmdLine, 
                    int CmdShow
                    )
{
    {
        WndClass.style          = 0;
        WndClass.lpfnWndProc    = WndProc;
        WndClass.cbClsExtra     = 0;
        WndClass.cbWndExtra     = 0;
        WndClass.hInstance      = hInstance;
        WndClass.hIcon          = ::LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor        = ::LoadCursor(NULL, IDC_ARROW);
        WndClass.hbrBackground  = (HBRUSH) ::GetStockObject ( WHITE_BRUSH );
        WndClass.lpszMenuName   = 0 ;
        WndClass.lpszClassName  = L"Clock";

        ::RegisterClass ( &WndClass );

        hWnd = ::CreateWindowEx( WS_EX_LAYERED | WS_EX_TOOLWINDOW,                          
             L"Clock",L"ClockGadget", WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, 0 );

        ::ShowWindow ( hWnd , CmdShow );
        ::UpdateWindow ( hWnd );

        // GDI+ initialisieren

        Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
        
        // ein Bild mittels GDI+ laden

        Gdiplus::Bitmap background (L"background.png");
        ::HBITMAP hBackground = 0;
        
        //HBITMAP holen

        background.GetHBITMAP ( Gdiplus::Color (),&hBackground);

        // wenn es einen Fehler beim laden gab abbrechen

        if ( !hBackground )
            return -1;

        // DC für den Hintergrund erstellen

        ::HDC hdcBackground = ::CreateCompatibleDC ( 0 );

        // Hintergrundbild auswählen

        ::SelectObject ( hdcBackground , hBackground);

        ::MSG msg;

        while ( AppRun )
        {
            // Messages

            while (::PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
            {   
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }

            // DC für zum rendern holen

            ::HDC hdcScreen = ::GetDC ( hWnd );

            // eine temporäre DC erstellen

            ::HDC hdcTemp = ::CreateCompatibleDC ( hdcScreen );

            // eine temporäre Bitmap erstellen

            ::HBITMAP hTempBitmap = ::CreateCompatibleBitmap ( hdcScreen , width,height);

            // Bitmap auswählen

            ::SelectObject ( hdcTemp , hTempBitmap);

            // Hintergrund in temporäre Bitmap bliten

            ::BitBlt (hdcTemp,0,0,width,height,hdcBackground,0,0,SRCCOPY);

            std::wstringstream str;

            // lokale Zeit holen

            ::GetLocalTime ( &SysTime );

            str << SysTime.wHour << ":" << SysTime.wMinute << ":" << SysTime.wSecond;
            std::wstring out = str.str ();

            // Auf DC schreiben

            Write ( hdcTemp , out , 25 , 25 , width , height );

            // Position des Fenstern holen

            ::GetWindowRect ( hWnd , &WindowRect );
            
            // und in POINT umwandeln

            ::POINT WindowPosition = { WindowRect.left , WindowRect.top };

            // Und nun aktualisieren

            ::UpdateLayeredWindow (hWnd,hdcScreen,&WindowPosition,&BackgroundSize,
                hdcTemp,&SourcePoint,0,&blendPixelFunction,ULW_ALPHA );

            // temporäre DC wieder zerstören

            ::DeleteDC ( hdcTemp );

            // DC zum rendern wieder freigeben

            ::ReleaseDC ( hWnd , hdcScreen );

            // ein wenig schlafen legen

            ::Sleep ( 500 );
        }

        // DC für Hintergrund wieder löschen

        ::DeleteDC ( hdcBackground );

    } // ist dafür da, dass Gdiplus::Bitmap vor GDI+ zerstört wird


    // GDI+ runterfahren

    Gdiplus::GdiplusShutdown(gdiplusToken);

    return 0;
}

::LRESULT CALLBACK WndProc (
                            ::HWND hwnd, 
                            ::UINT message, 
                            ::WPARAM wParam, 
                            ::LPARAM lParam
                            )
{
    switch ( message )
    {
    case WM_KEYDOWN:
        {
            switch ( wParam )
            {
            case VK_ESCAPE:
                // escape zum beenden

                ::SendMessage ( hwnd , WM_CLOSE , 0 , 0 );
                break;
            };
        }break;

    case WM_CLOSE:
        {
            AppRun = false;
            ::PostQuitMessage ( 0 );
        }
        break;

    case WM_LBUTTONDOWN:
        {
            // damit das Gadget überall gepackt und verschoben werden kann

            ::SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION,NULL);
        }break;
    }

    return ::DefWindowProc (hwnd, message, wParam, lParam);
}


Ich weiss nicht so recht, was ich zu dem Code sagen soll. Grundsätzlich sollte da nichts kompliziertes dabei sein, wenn man einmal ein Windowsprogramm geschrieben hat, ausser, dass man einfach wissen muss, wie man vorgehen sollte, wenn man mit UpdateLayeredWindow arbeitet.
Ich probiere einmal das Problem zu schildern und was man dagegen machen kann, respektive was ich da in meinem Code gemacht habe.

[list]1. Man muss alles, was man auf den Screen bringen will genau mit einem UpdateLayeredWindow (im folgenden ULW genannt )aufruf drauf bringen. Das heisst, dass alle Zeichenvorgänge direkt auf die Screen DC ( in meinem Code hdcScreen genannt) schon einmal wegfallen.

2. Das bedeutet, dass wir alle Zeichenvorgänge in eine zweite DC machen müssen ( hdcTemp ), welche wir dann per ULW schön auf den Screen bringen.

3. Alle Zeichenvorgänge (ausser des Hintergrundes , hdcBackground ) auf die hdcTemp müssen durch die WinAPI-Funktion AlphaBlend geschehen, da sonst Informationen des Hintergrundes ( hdcBackground ) verloren gehen. (Zum ausprobieren einfach einmal in der Funktion Write() , TextOut ( hdcTempFont ...) direkt auf das Ziel zeichnen lassen also hdcTempFont mit hdcDest ersetzen und schauen, was passiert)

4. Punkt 3 bringt uns auf ein anders Problem, nämlich, dass WinAPI - Funktionen, wie TextOut oder LineTo nunmal keine Unterstützung für Alpha haben, also muss da auch wieder eine temporäre DC herhalten, von welcher aus wir dann mit Alpha in die temporäre DC ( hdcTemp ) zeichnen können.[/list]
Ein weiteres Problem, dass sich mir aufgetan hat, war, dass sich aus mir unerklärlichen Gründen die eigens erstellte DC anderst Verhalten hat, wenn ich SetMapMode () darauf angewandt habe. ( Ist aber für die meisten Gadgets irrelevant und somit nicht wichtig, aber einfach nur so als kleine Anmerkung, falls da auch jemand darauf stösst und einmal eine Erklärung findet..)


4. Referenzen
GDI+
GDI

EDIT
- Hintergrundbild hinzugefügt. :)
- mehr oder weniger komplett Überarbeitung des Textes.
- libs linken
- Professional / Express Edition

Anonymous

unregistriert

2

18.11.2008, 11:17

Sei mir nicht böse, aber ich kenne keine Windows Gadgets und kann daher nicht sehr viel Anfangen. Auch nicht wieso Gadgets PeekMessage benutzen oder was die Besonderheiten dafür sind?

Mir fehlt auch ein Bild vom "Fertigen Produkt". Also irgendwie empfinde ich es noch als recht unfertig oder lieblos, hoffe aber Ersteres, da man das noch verfeinern kann. ;)

drakon

Supermoderator

  • »drakon« ist der Autor dieses Themas

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

3

18.11.2008, 12:21

Naja. Soo viel kann man mit denen üblicherweise auch nicht anfangen. Ich habe einfach mal das Gadget von Vista gesehen, welches die Auslastung der CPU in Form eines Tachometers anzeigt. Und da habe ich dann gedacht, dass so ein Ding auch mal unter XP haben will. (Wahrscheinlich gibt es das auch schon irgendwo zum Download, aber ich wollte es halt auch mal selber machen). Und da im anderem Forum jemand gefragt hat, wie man Screenmates machen kann dachte ich, dass ich da halt mal ein kleines Tutorial dazu schreibe.

PeakMessage habe ich genommen, weil ich ursprünglich ja eine Anzeige hatte, weil ich selber bestimmen wollte, wie viel mal geupdatetet wird.

OK. Ein wenig unfertig ist es vlt. schon, habe es heute während den Pausen geschrieben, aber ich weiss ehrlich gesagt nicht gross, was ich da noch anderst machen soll. Klar könnte ich das ganze noch schön in Klassen, schöne Funktionen usw. verpacken, aber das hilft nicht unbedingt der Verständniss. Soll ja nur als Grundgerüst dienen, damit man ein wenig damit rumspielen kann, da meiner Meinung nach das ganze nicht immer so läuft, wie man zuerst erwarten könnte.

Also das Fertige Produkt sollte ja das sein, was man am Schluss hat, wenn man den Code kompiliert. ;) (den Hintergrund habe ich ja auch Mitgeliefert). Und viel mehr, als dass da dann der Hintergrund mit der aktuellen Zeit (natürlich fortlaufend) drin angezeigt wird ist ja auch nicht zu sehen.

Aber danke für die Kritik. Kannst ja mal noch ein wenig genauer ausführen, was du meinst, dass ich noch anpassen sollte.

Anonymous

unregistriert

4

18.11.2008, 13:31

Nein, mir geht es weniger um den Quelltext, sondern um den "normalen" Erklärungstext. Ich kenne keine Windows Gadgets, die sagen mir absolut gar nichts - weil ich kein Vista habe.

Drum wäre es fein wenn du die Dinger mal erklärst. Denn so zeigst du Leuten wie man die Dinger codet, aber sagst nicht was das für Teile sind und was es für "Besonderheiten" gibt und was zu beachten usw.

Guck doch z. B. einfach mal hier:
https://www.spieleprogrammierer.de/phpBB2/viewtopic.php?t=9538

Da erkläre ich ganz einfach mal, wie man auf Variablen zugreift und das die irgendwo im Speicher liegt. Ich erwähne das sie irgendwo im Speicher liegt und man auf diese Zugriff haben kann, das ist eher der Punkt. So etwas fehlt bei deinem Tutorial etwas. Es hat so den Flair wie "Mit der Tür in der Hand ins Zimmer stürmen". ;)

Also daran wurde meiner Meinung nach extrem viel Potenzial verschenkt.

drakon

Supermoderator

  • »drakon« ist der Autor dieses Themas

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

5

18.11.2008, 13:33

Zitat von »"unsigned long"«

....


Hehe. OK. Dann gibts da noch einen Text dazu. :)

Anonymous

unregistriert

6

18.11.2008, 13:37

drakon
Am besten nicht nur einen, sondern viele.

Du musst immer bedenken: Du willst Leuten, die etwas NICHT wissen beibringen - also solltest du es ihnen auch sehr gut erklären und nicht nur "hinklatschen" und sagen "macht mal" ;) Klingt etwas hart, aber so ist es auch bei jedem Tutorial das ich geschrieben habe.

Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

7

18.11.2008, 14:06

Du solltest vlt noch sagen, dass man

gdiplus.lib
MSImg32.Lib

dazulinken muss ;)
Nicht jeder hat so viel lust immer nach diesen libs zu suchen ;)

Aber sonnst find ichs nicht schlecht :)

drakon

Supermoderator

  • »drakon« ist der Autor dieses Themas

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

8

18.11.2008, 15:00

@unsigned long
Ein wenig Eigeninitiative erwarte ich schon auch. ;)

@Databyte:
thx, das habe ich ganz vergessen.

Nach dem Code habe ich jetzt noch eine genauere Begründung angegeben. Ist die soweit verständlich? Die Zusammenhäng sind halt doch recht eng und somit nicht so einfach getrennt zu erkären.

9

18.11.2008, 17:13

Nix für ungut drakon, aber Gadgets mit C++ zu proggen, ist (imho) mit Kanonen auf Spatzen schießen.
Mit ein wenig Script, CSS, XML und Co geht es doch auch!?

http://www.drweb.de/magazin/vista-gadgets-erste-schritte/

Toa

Alter Hase

Beiträge: 944

Beruf: Research associate

  • Private Nachricht senden

10

18.11.2008, 18:05

trotzdem sehr interessanter Artikel und außerdem etwas Aktuelles! Deshalb Daumen hoch von mir ^.^

Werbeanzeige