Forum > Windows

WinAPI - examples - post 7

(1/3) > >>

440bx:
This post includes examples for a number of API functions but because they are also meant to show how to deal with flicker, the main APIs used in each example is listed.  The APIs are:


WinAPI - Rectangle                               (in Rectangle)
WinAPI - CreateSolidBrush         
WinAPI - CreatePen               
WinAPI - CheckMenuRadioItem       
WinAPI - GetStockObject           
WinAPI - DeleteObject             


WinAPI - CreateWindowEx                    (in FlickerMax)
WinAPI - GetClassInfoEx           
WinAPI - RegisterClassEx         
WinAPI - BeginPaint               
WinAPI - EndPaint                 
WinAPI - SelectObject             
WinAPI - SetBkColor               
WinAPI - GetTextExtentPoint32     
WinAPI - SetTextAlign             
WinAPI - TextOut                 
WinAPI - DialogBox               
WinAPI - EndDialog               
WinAPI - DestroyWindow           
WinAPI - PostQuitMessage         


WinAPI - CopyRect                                  (in FlickerMin)
WinAPI - Rectangle               


WinAPI - GetClassLongPtr                      (in HREDRAW_VREDRAW)
WinAPI - SetClassLongPtr         
WinAPI - GetMenuState             
WinAPI - CheckMenuItem           


WinAPI - CreateSolidBrush                     (in Gradient)
WinAPI - DeleteObject             
WinAPI - GetDC                   
WinAPI - ReleaseDC               
WinAPI - MulDiv                   
WinAPI - FillRect                 
WinAPI - PostMessage             
WinAPI - CheckMenuRadioItem       
WinAPI - InvalidateRect           


WinAPI - GetClipBox                              (in GetClipBox)
WinAPI - CopyRect                 
WinAPI - SetRectEmpty             
WinAPI - SetTimer                 
WinAPI - KillTimer               
WinAPI - BringWindowToTop         
WinAPI - SetForegroundWindow     
WinAPI - GetWindow               
WinAPI - GetWindowDC             
WinAPI - UpdateWindow             
WinAPI - Rectangle               

WinAPI - GetClipBox                             (in IsWindowFullyCovered)
WinAPI - SetTimer                 
WinAPI - KillTimer               
WinAPI - BringWindowToTop         
WinAPI - SetForegroundWindow     
WinAPI - InvalidateRect           
WinAPI - UpdateWindow             
WinAPI - GetClassLongPtr         
WinAPI - CopyRect                 
WinAPI - Rectangle               

WinAPI - GetParent                               (in GetParent)
WinAPI - GetAncestor             
WinAPI - GetConsoleWindow         
WinAPI - GetConsoleProcessList   
WinAPI - GetDC                   
WinAPI - ReleaseDC               
WinAPI - GetWindowRect           
WinAPI - GetTopLevelWindow       
WinAPI - IsTopLevelWindow         
WinAPI - SetWindowPos             
WinAPI - MoveWindow               
                int 3                   


WinAPI - EnumWindows                      (in EnumWindows)
WinAPI - GetWindowThreadProcessId
WinAPI - IsWindowVisible         
WinAPI - GetWindowText           
WinAPI - SendMessage             
WinAPI - InitCommonControls       
WinAPI - CreateStatusWindow       
WinAPI - GetClassLongPtr         
WinAPI - SetClassLongPtr         
WinAPI - CreateWindowEx           
WinAPI - GetDlgItem               
WinAPI - MoveWindow               
WinAPI - GetScrollPos             


WinAPI - EnumChildWindows             (in EnumChildWindows)
WinAPI - EnumWindows             
WinAPI - GetAncestor             
WinAPI - GetDesktopWindow         
WinAPI - IsWindowVisible         
WinAPI - GetWindowText           
WinAPI - GetWindowThreadProcessId


in addition to the APIs listed above, this post and the following ones focus on the reduction or elimination of flicker.

NOTE: the example programs are attached to the posts below that refer to how they work.


Continued on next post...

440bx:
Continued from previous post...

Flicker is mostly caused by three (3) characteristics, they are: the presence of CS_HREDRAW and CS_VREDRAW in the window class and the processing of the WM_ERASEBKGND (erase background) window message.

The CS_HREDRAW and CS_VREDRAW cause the window's entire client area to be invalidated when the window is resized.  This in turn causes the entire background of the window to be erased by the default window procedure when a WM_ERASEBKGND message is received.  It is the full erasing of the background that is the source of flicker.  It causes two different images to appear, the first image is the background (for a very short time) the second one is the image that is supposed to be painted on it.

Carefully controlling _what_ has to be erased when a WM_ERASEBKGND message is received can significantly reduce flicker and often eliminate it completely.

There are a number of ways to reduce or eliminate flicker, of those, double buffering is likely the most common one due to the fact that it is fairly simple to implement and it completely eliminates flicker.  Double buffering consists of painting on a bitmap that is compatible with the output device (usually but not always the screen) then bitblt-ing the fully drawn bitmap onto the output device without having to erase the background first, which is not required because the bitmap is the full and final image that should appear on the output device.

Double buffering, while very effective is not without disadvantages, among them, it requires some code to implement it, often specific for each window that uses it and, it can potentially use a fair amount of memory.  In addition to that, it has the potential of noticeably lowering the speed at which the windows that use it are repainted.  This can be experienced when a program's window feels "elastic" when being resized.  The more windows on the user interface, the greater the potential for the window to feel "elastic" when resized.


The FlickerMax example is what happens when the window class includes CS_HREDRAW and CS_VREDRAW and the default window procedure is allowed to erase the background when processing the WM_ERASEBKGND.  The resulting amount of flicker is substantial and visually offensive.  It can be seen in its greatest "glory" when the window is resized horizontally.  FlickerMax uses the following code when registering the class:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  if not GetClassInfoEx (hInstance, AppName, cls) then  begin    with cls do    begin      { cbSize has already been initialized as required above                 }       style           := CS_BYTEALIGNCLIENT or CS_VREDRAW or CS_HREDRAW;      lpfnWndProc     := @WndProc;                    { window class handler  }      cbClsExtra      := 0;      cbWndExtra      := 0;  and does not process the WM_ERASEBKGND thus letting the default window procedure erase the entire client area.

The FlickerMin example is code-wise almost identical to FlickerMax, the difference is that FlickerMin does _not_ allow the default window procedure to erase the background, instead, it erases the rectangles that are above and below the text when processing the WM_PAINT, followed by outputting the line of text.  That small difference greatly reduces the amount of flicker.   Note that the flicker isn't totally eliminated in this case, resizing the FlickerMin horizontally makes it easy to notice but, for most purposes, it can be considered tolerable.

FlickerMin is the reason for the Rectangle example.  When using the Rectangle API it is important to keep in mind the type of pen that API uses to draw the edges of the rectangle.  When the pen is a NULL pen the rectangle drawn will be one (1) pixel smaller horizontally and vertically than the specified rectangle coordinates.  This is because Rectangle uses the pen to draw the outer right and bottom edge of the rectangle and, when the pen is NULL this causes those edges not to be drawn resulting in a rectangle that is one (1) pixel short on the right and bottom edges.

This can be seen with the Rectangle example by changing the pen used from a null pen to any other colored pen back and forth.  A zoomed image of the bottom right of the resulting rectangle is attached where the difference between using a null pen and a colored pen is easier to see.

When using the Rectangle API to erase parts of the client area background, the simplest method consists of specifying a null pen and increasing the Right and Bottom coordinates of the rectangle.  This is what FlickerMin does to erase the part of the client area that is above and below the text without erasing any portion where the new text should appear.

The little bit of flicker seen in FlickerMin is due to its reliance on ExtTextOut to clear the narrow and wide rectangle where the text is displayed. It is possible to make the flicker imperceptible by using Rectangle to erase the area on the left and right of where the text will appear (in addition to above and below) and instruct ExtTextOut to confine its output to the rectangle that is snug to the text. (that improvement is left as an exercise for the reader ;) - simply two additional calls to Rectangle and a change in the rectangle used by ExtTextOut)

FlickerMin uses the same code as FlickerMax when registering the window class but, when it gets a WM_ERASEBKGND it tells Windows that it took care of erasing the background by using the following code:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---    WM_ERASEBKGND:    begin      { tell Windows we erased the background                                 }       WndProc := 1;       exit;    end;then when processing the WM_PAINT it uses:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---       {-----------------------------------------------------------------------}      { clear the rectangles above and below the text                         }       { this could have been done when processing the WM_ERASEBKGND message   }      { but it's more convenient to do here.                                  }       CopyRect(ClearRect, ClientRect);       { clear the rectangle above the text                                    }       with ClearRect do      begin        { Left, Right and Top are already set                                 }         Bottom := TextRect.Top;        Rectangle(ps.hdc, Left, Top, Right, Bottom);      end;       { clear the rectangle below the text                                    }       with ClearRect do      begin        { Left and Right are already set                                      }         Top    := TextRect.Bottom;        Bottom := ClientRect.Bottom;         Rectangle(ps.hdc, Left, Top, Right, Bottom);      end;    which erases the areas above and below the text.  A couple of additional calls to rectangle could be used to erase the areas to the right and left of the text that _will_ be output to make flicker imperceptible.


One of the most important decisions is determining if CS_HREDRAW and CS_VREDRAW are necessary when registering the window class.  It is, unfortunately, somewhat common to see windows include those two (2) styles in the window class when they aren't really necessary.

The HREDRAW_VREDRAW is an example.  it does NOT include the CS_HREDRAW and CS_VREDRAW styles, yet it updates the window correctly and without a trace of flicker without using any methods to prevent flicker.  The example includes the option of adding CS_HREDRAW and CS_VREDRAW in the class styles, when that is done, there will be a very significant amout of flicker.

Again, HREDRAW_VREDRAW does NOT include any code at all to prevent flicker. The exclusion of CS_HREDRAW and CS_VREDRAW is all that is needed to have no flicker whatsoever when resizing the window.

The CS_HREDRAW and CS_VREDRAW styles need to be included when what is displayed in the client area changes depending on the size of the window.  The Gradient is an example of this.  Specifically, the color used at a particular location changes depending on the size of the window, because of this, the entire window needs to be invalidated and repainted whenever its size changes.

The Gradient program draws everything when processing the WM_ERASEBKGND and tells Windows that it took care of erasing the background.  This is just to show that, at least in some cases, it is possible to repaint the entire client area when processing the WM_ERASEBKGND.  However, a "normal" program would simply return one (1) when processing WM_ERASEBKGND and redraw the gradient when dealing with WM_PAINT.

NOTE: the gradient program uses functions to draw the vertical and horizontal gradients.  It would be fairly easy to roll them into a single function that uses an "orientation" parameter to select vertical or horizontal.  Gradient uses different functions to use a different number of bands depending on the orientation.  The vertical orientation uses fewer bands resulting in noticeable banding while the horizontal orientation uses more bands thereby making the gradient band-less (much smoother.) Also, the difference in smoothness is more apparent when the gradient is blue than when it is grey.
 
ETA:
The HREDRAW_VREDRAW was inadvertently left out.  It is attached to a later post. 

Continued on next post ...

440bx:
Continued from previous post...

The GetClipBox example is meant to show that the mechanisms Windows uses to paint a window's client area changed after Windows XP.  When run on WinXP the example will work as expected but won't on Vista and later versions UNLESS its compatibility setting has "Disable visual themes" checked.

if "Disable visual themes" is not checked then GetClipBox will report "COMPLEX_REGION" whenever the window is partially visible and "NULL_REGION" when the window is fully covered by other windows.

if "Disable visual themes" is checked then GetClipBox can also report "SIMPLE_REGION" if the window's visible area is a rectangle (which may be the entire client area or a portion of the client area.)  NOTE that when "Disable visual themes" is checked the window caption will look like a Win95 caption instead of the stylish captions shown by Windows Vista and later.

Given that it is unreasonable to require an application to only operate properly when "Disable visual themes" is checked, the current usefulness of GetClipBox is rather limited.

GetClipBox is still useful, regardless of the "Disable visual themes" setting, to determine if a window is fully covered by other windows or not.  When the window is fully covered by other windows then GetClipBox will always return "NULL_REGION", this can be useful to applications that update the contents of the client area on a regular basis, using a timer, such as the "performance" tab of the task manager (or the GetClipBox program itself.)  If the window is fully covered then there is obviously no need to update the client area thus sparing Windows from wasting clock cycles to determine that whatever the application is painting should be ignored.

The GetClipBox example also shows how to eliminate flicker by telling Windows the background has been erased and using the Rectangle API to erase only what is required of the background.  The result, unlike in FlickerMin that uses the same method, is totally flicker free.

In addition to that, it shows how to use BringWindowToTop and SetForegroundWindow to bring the main window to the foreground when a mouse click occurs in the popup's client area.  To see this working, have some window cover the GetClipBox main window then click anywhere in the popup's client area.  That will cause the main window to be placed back at the top of the Z order.

NOTE: the popup's topmost attribute is set in CreateWindowEx.


The IsWindowFullyCovered example is an "abbreviated" version of GetClipBox that simply reports if the window is fully covered or not.

It is simply the "home" of a function "surprisingly" namedd IsWindowFullyCovered which can be copy/pasted into any other program that needs the functionality.


The GetParent program is to show how the APIs GetParent, GetAncestor and GetWindow behave depending on the type of window (toplevel, child, popup) where it is being called.  The MSDN documentation, while it tries hard, it doesn't make it easy to visualize what the result of those APIs is going to be depending on the type of window using it.  The GetParent program _shows_ the result in each case.

The GetParent program is actually three (3) programs in one.  The first shows the results when the owner of the popup window is zero (0).  That occurs when none of the {$define} in the code are active/set.  The second one shows the results when a grandchild window is set as the popup owner, controlled by the {$define POPUP_OWNER_IS_GRANDCHILD}, the third one shows the results when the owner is set using the TopLevelWindow API, controlled by the {$define POPUP_OWNER_IS_TOPLEVELWINDOW}.

In each case note carefully, the values shown GetParent, GetWindow(..., GW_OWNER) and the values returned by the various GetAncestor options.  Particularly when the popup's owner is the grandchild window.

The GetParent example also shows how to pass a parameter to the WM_CREATE message using the lParam parameter of CreateWindow/CreateWindowEx (their last parameter.)  GetParent uses only one (1) window procedure and only one class for the main window, child window, grandchild window and popup window.  This causes Window's default window procedure to call the window procedure recursively for the creation of each window.  The recursion is controlled and ended using the lParam which determine the type of window to create (main, child, grandchild or popup.)

It also uses the WM_ACTIVATE message to make the three (3) windows to be brought up to the top of the Z order whenever either the main window or the popup window is activated.  For this it uses SetWindowPos.  It is worth noting that using SetWindowPos always works with the GUI windows but, for some unknown reason, does NOT always work with console windows (it only works _most_ of the time.)  It does this using the following code:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---    WM_ACTIVATE:    begin      if LOWORD(wParam) <> 0 then    { window is being activated              }      begin        if Wnd = MainWnd then        begin          { the main window is being activated, make the popup window visible }           SetWindowPos(PopupWnd,                       HWND_TOP,                       0,                       0,                       0,                       0,                       SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);           if ConsoleWnd <> 0 then          begin            { NOTE: with consoles SetWindowPos works only MOST of the time.   }            {       the reason why it doesn't work all the time is unknown.   }             SetWindowPos(ConsoleWnd,                         HWND_TOP,                         0,                         0,                         0,                         0,                         SWP_ASYNCWINDOWPOS or                         SWP_NOACTIVATE     or SWP_NOMOVE or SWP_NOSIZE);          end;           exit;        end;         if Wnd = PopupWnd then        begin          { the popup window is being activated, make the main window visible }           SetWindowPos(MainWnd,                       HWND_TOP,                       0,                       0,                       0,                       0,                       SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);           if ConsoleWnd <> 0 then          begin            { NOTE: with consoles SetWindowPos works only MOST of the time.   }            {       the reason why it doesn't work all the time is unknown.   }             SetWindowPos(ConsoleWnd,                         HWND_TOP,                         0,                         0,                         0,                         0,                         SWP_ASYNCWINDOWPOS or                         SWP_NOACTIVATE     or SWP_NOMOVE or SWP_NOSIZE);          end;           exit;        end;      end;    end;Note that SWP_NOACTIVATE is specified in the call to SetWindowPos, failing to specify that flag will cause the main window and popup window to recursively activate each other, leading to an undesirable situation.

Another "interesting" feature of GetParent is how it determines whether or not it should move the console window.  If GetParent is started from a command line prompt, it will NOT move the console it was started from.  However, if it was NOT started from a command line prompt, it creates its own console (automatically done by Windows because in that case GetParent is a console application) and proceeds to move it to uncover it (make it visible.)  It makes the determination using the following code:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure PositionDebugConsole(Wnd : HWND);  { positions the console window to ensure it is not obscured by this         }  { program's main window or its popup.  NOTE: this procedure is NOT general  }  { it uses knowledge of the total height of the text output in the windows.  }var  ProcessCount : DWORD;  ProcessList  : array[1..10] of DWORD;   WindowRect   : TRECT;  ConsoleRect  : TRECT; begin  { if the main window has not yet been created, there is no need to move the }  { console window.                                                           }   if Wnd = 0 then exit;   { the main window exists, figure out if we need to move the console         }   if GetConsoleWindow() = 0 then  begin    { there is no console, therefore there is nothing to position             }     exit;  end;   ZeroMemory(@ProcessList, sizeof(ProcessList));   ProcessCount := 0;  ProcessCount := GetConsoleProcessList(@ProcessList, high(ProcessList));   if ProcessCount > 1 then  begin    { we were started from a pre-existing console, not from a console we own  }    { therefore, don't alter the position of the console, it would not be     }    { "polite" to move a pre-existing console.                                }     exit;         { existing console remains whereever it currently is        }  end;   { we own the console, move it to be right next to our window and below the  }  { popup.                                                                    }   ConsoleWnd := GetConsoleWindow();         { save the handle to the console  }   GetWindowRect(Wnd,        WindowRect);  GetWindowRect(ConsoleWnd, ConsoleRect);   with ConsoleRect do  begin    MoveWindow(ConsoleWnd,                  { our dedicated console window    }               WindowRect.Right,            { x snug to the main window       }               2 * LabelsHeight,            { below the popup window          }               Right  - Left,               { no change in width              }               Bottom - Top,                { no change in height             }               TRUE);                       { repaint it                      }  end;end;It makes the determination using GetConsoleProcessList which will return a value greater than 1 if it was started from an existing console and 1 otherwise (it is a new console.)


GetParent has another notable and unexpected "peculiarity".  For some reason, the messages (hints) emitted by the compiler (v3.0.4) are off by one. Specifically, the line numbers are one greater than the real line number.  This is probably the cause why when, in Lazarus, placing a breakpoint at some line, execution stops one line _after_ the breakpoint instead of the line that contains the breakpoint.  Reason for this behavior is unknown but, it might be a bug in FPC v3.0.4.


Continued on next post...

440bx:
Continued from previous post...


WinAPI - EnumWindows
WinAPI - EnumChildWindows

The EnumChildWindows program is _very close_ to the same program/example recently posted in another thread.  However, there are two important differences.

The first one is that one of the constants used by GetAncestor was misspelled, this didn't cause a problem because it was defined for Delphi v2.0 only and it was not used anyway.

The second difference is that the original example did NOT account for Windows ever returning a handle whose value exceeds high(DWORD) (all window handles should fit in a DWORD even in a 64 bit installation because window handles can and are shared among 32bit and 64bit programs.)  For some unknown reason, there are times when EnumWindows and EnumChildWindows will return a window handle that has the high DWORD set to $FFFFFFFF.  MS' spy++ also sees the presence of $FFFFFFFF in the high DWORD thus proving it is not a result of a bug in the example.

The presence of $FFFFFFFF in the high DWORD is a problem in EnumWindows and EnumChildWindows because it messes up the alignment of the output in the listbox.

This version of EnumChildWindows unceremoniously sets the high DWORD of a window handle to zeroes when necessary thus ensuring the window handle values never exceed high(DWORD) thus not messing up the alignment in the listbox.


ABOUT HINTS and UNUSED PARAMETERS

This set of programs aim to minimize the number of unwanted hints emitted by FPC, specifically those related to some variable not being initialized because it presumes that "var" parameters should be initialized and, those caused by unused parameters.

Removing the unwanted hints about variables not being initialized due to parameters being "var" instead of "out" is solved by correcting the definition. This set of example uses corrected definitions whenever necessary.

Removing the unwanted hints about parameters being unused is done using the following code:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$ifdef FPC}  procedure UNUSED_PARAMETER(UNUSED_PARAMETER : ptrint); inline; begin end;{$endif} and using that procedure to actually use the otherwise unused parameter, as in:
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  {$ifdef FPC}    UNUSED_PARAMETER(lParam);           { "declare" lParam as unused          }  {$endif}that will still cause one (1) hint to be emitted due to the procedure not using the parameter but, that procedure can be used for every unused parameter in the program (in a large program, there can be a good number of them) thus getting only one (1) hint instead of one per unused parameter.

Additionally, since the procedure is inline and is empty, it generates no code.

Bottom line: it is very similar to using UNREFERENCED_PARAMETER in C.



OTHER CONSIDERATIONS:

There are a good number of ways to avoid and/or minimize flicker.  This set of examples will be followed by another set that uses other methods/techniques.



Enjoy!

Alextp:
Hello. It will be good info for the wiki. I can help--I can make the wiki page + post your examples to Github and give links in wiki.

All examples.

Navigation

[0] Message Index

[#] Next page

Go to full version