Thursday, April 23, 2009

Potential memory leaks by initializing a record (revised)

My major focus is now on D. It is a nice language with great features. Template, Mixins, Array-Slicing, functional programming and more OOP. I am involved in a D-project, which is intended to redesign an old system (written in C) using D.

My teammates give me a lot of advices on design principles. One thing just inspired me. so I revised my old article "Potential memory leaks by initializing a record".

The main point: the default way of initializing a record looks more like an abuse of FillChar. I suggest declare a const record (eg. EmptyRecord) instead of using FillChar.
 

Thursday, October 23, 2008

LIKE is not case insensitive?

I was stuck by understanding the following SQL query:


SELECT name
FROM my_table
WHERE name LIKE '%Jack%'

There is nothing special. It just tries to pick up all "names" of "my_table", those values contain the word "Jack" (case sensitive). However, "jack" is also in the result. I cannot believe my eyes.

I googled "LIKE case insensitive" and got the answer:
The problem is caused by the collation of the database. The default collation of my database is "latin1_swedish_ci". The trailing "_ci" stands for case insensitive. I changed it to "utf8_bin", the problem has been solved.

Here are some useful links:
http://forums.mysql.com/read.php?20,202839,202839
http://dev.mysql.com/doc/refman/5.0/en/pattern-matching.html
http://dev.mysql.com/doc/refman/5.0/en/charset-charsets.html

Wednesday, September 10, 2008

require_once()

If you used require_once(), you may have known one big issue of it: "If you give a relative path to the file you want to require, PHP will use the working directory as root, not the directory where this file is located." Your code won't have any problem, except the file to be required, requires some other files as well and unfortunately they are not in a same directory.

There are several solutions on the Internet.

  1. Put all PHP files into one folder. This works for tiny projects, but a flat folder structure is really ugly.
  2. Use a PHP framework, like Zend, CodeIgniter, CakePHP. They provide a certain function like $this->load() to find the correct path of these files and load them.
  3. If your code is not based on a framework, you can simply use
    "require_once(dirname(__FILE__).'/path_to_file')" instead of
    "require_once('path_to_file')". However this does not work on my xampp environment. My code hangs up when executing this line. (The problem seems not to be on the slash or backslash. Really no idea)

My Solution

Write a function to calculate the relative path from file directory to working directory. If you previously use "require_once('path_to_file')", now you write
"require_once(cp('path_to_file'))". It has been tested on Windows.

if (!function_exists('cp'))
{
function cp($f) // change_path
{
if (($wp = getcwd()) === ($fp = dirname(__FILE__))) { return $f; }
$wp = explode('\\', $wp);
$fp = array_merge(explode('\\', $fp), explode('/', $f));
for ($i = 0, $p = min(count($wp), count($fp)); $i < $p; $i++)
{
if ($wp[$i] === $fp[$i])
{
array_shift($wp);
array_shift($fp);
$i--; $p--;
continue;
}
break;
}
$p = ''; foreach ($wp as $k) { $p .= '../'; }
return $p.implode('/', $fp);
}
}

Saturday, August 9, 2008

Quick note on UnicodeString

Jan Goyvaerts has introduced some key information about Unicode in Delphi 2009.
http://www.micro-isv.asia/2008/08/get-ready-for-delphi-2009-and-unicode/

Marco Cantu has made a performance comparison between AnsiString and UnicodeString.
http://blog.marcocantu.com/blog/tiburon_unicode_video_5.html

Conclusion

According to the information, so much I have collected in the past a few months, I summarized them as the following points:
  1. Simple projects can be upgraded to Delphi 2009 without any modifications. Projects with heavy string operations or file operations could be compiled as well. But they should be tested completely before release.
  2. UnicodeString is based on the same concept as AnsiString. So it is very fast and efficient, although it is a little bit slower than AnsiString.
  3. In compare with WideString much less memory will be allocated for UnicodeString. If it is not necessary, you had better switch WideStrings to UnicodeStrings for a performance boost.

UPDATE 1: Here and here are detailed performance tests
 

Tuesday, May 27, 2008

Do not give the OK button a keyboard accelerator

Raymond Chen, a well-known developer on the Windows Shell team at Microsoft, mentioned a principle about dialog design in his blog: Do not give the OK button a keyboard accelerator [1].

But it is funny, that even their own products do not follow this principle strictly. A remarkable example is the "Restart Prompt Dialog of Automatic Updates" in WindowsXP. Both buttons "Restart Now" and "Restart Later" have a keyboard accelerator. If this dialog pops up, while I am pressing ALT+N, the current Windows session will be terminated immediately. And there is no Postpone button either. This button appears in Windows Vista, but there is no option "Do not prompt me again, I will restart my computer later.". If I do not intend to restart my computer very soon, I have to press "Postpone" in every 4 hours. It is really annoying! Not only the unfriendly design, but also infinite critical Windows updates. The only one option for me is to turn off "Automatic Updates".

According to the great design principles [2]: I suggest remove those keyboard accelerators and add a 10-second-delay before restarting Windows, so that user can cancel this action. This is called user experience!

References

  1. Gentle reminder: On a dialog box, do not give OK and Cancel accelerators
  2. How to Design a Great User Experience
 

Monday, May 26, 2008

Form disappeared

After I started working on dual monitors, I have found another interesting issue:

  1. Create a new application with a main form and a sub-form.
  2. Set the property Position of the sub-form to poMainFormCenter
  3. Create a button "Show Sub-Form" on main form.
  4. Move more than 50% area of the main form out of the main screen.
  5. Click the button, we created.
The sub-form disappeared! Because the popup position of sub-form is out of the screen. I did a similar test with Internet Explorer. Its dialog will be shown at the corner of the screen. It can be regarded as a bug, so I have reported this issue to QualityCentral [1] as well.

Solution

This time you have to modify Forms.pas
  1. Copy Forms.pas to your project folder,
  2. find and replace with the following code,
  3. and then recompile your project again.

{$IFDEF FIXUP_FORM_POPUP_POSITION}
function GetPrimaryMonitor: TMonitor;
var
I: Integer;
begin
for I := 0 to Screen.MonitorCount - 1 do
begin
Result := Screen.Monitors[I];
if Result.Primary then
Exit;
end;
Result := Screen.Monitors[0];
end;

procedure CenterFormToPrimaryMonitorCenter(var X, Y: Integer;
AForm: TCustomForm);
var
R: TRect;
begin
R := GetPrimaryMonitor.WorkareaRect;
X := R.Left + (R.Right - R.Left - AForm.Width) div 2;
Y := R.Top + (R.Bottom - R.Top - AForm.Height) div 2;
end;

procedure SnapFormToMonitorEdgeOnDemand(var X, Y: Integer;
AForm: TCustomForm);
var
DistanceLeft, DistanceRight: Integer;
CenterMon: TMonitor;
R: TRect;
begin
CenterMon := Screen.MonitorFromPoint(
Point(X + AForm.Width div 2, Screen.DesktopHeight div 2));
R := CenterMon.WorkareaRect;
// Adjust X-pos
DistanceLeft := X - R.Left;
DistanceRight := R.Right - (X + AForm.Width);
if (DistanceLeft < 0) and (DistanceRight < 0) then
begin
if DistanceLeft >= DistanceRight then
X := R.Left // Snap to left edge
else X := R.Right - AForm.Width; // Snap to right edge
end
else if (DistanceLeft < 0) or (DistanceRight < 0) then
begin
if DistanceLeft < 0 then
X := R.Left // Snap to left edge
else X := R.Right - AForm.Width; // Snap to right edge
end;
// Adjust Y-pos
if Y < R.Top then
Y := R.Top
else if Y > R.Bottom - AForm.Height then
Y := R.Bottom - AForm.Height;
end;
{$ENDIF}

procedure TCustomForm.CMShowingChanged(var Message: TMessage);
// ...
if (FPosition = poScreenCenter) or
((FPosition = poMainFormCenter) and (FormStyle = fsMDIChild)) then
begin
if FormStyle = fsMDIChild then
begin
X := (Application.MainForm.ClientWidth - Width) div 2;
Y := (Application.MainForm.ClientHeight - Height) div 2;
end else
begin
{$IFDEF FIXUP_FORM_POPUP_POSITION}
CenterFormToPrimaryMonitorCenter(X, Y, Self);
{$ELSE}
X := (Screen.Width - Width) div 2;
Y := (Screen.Height - Height) div 2;
{$ENDIF}
end;
{$IFDEF FIXUP_FORM_POPUP_POSITION}
SnapFormToMonitorEdgeOnDemand(X, Y, Self);
SetBounds(X, Y, Width, Height);
//SetWindowToMonitor() will cause unexpected popup position change!
{$ELSE}
if X < Screen.DesktopLeft then
X := Screen.DesktopLeft;
if Y < Screen.DesktopTop then
Y := Screen.DesktopTop;
SetBounds(X, Y, Width, Height);
if Visible then SetWindowToMonitor;
{$ENDIF}
end
else if FPosition in [poMainFormCenter, poOwnerFormCenter] then
begin
CenterForm := Application.MainForm;
if (FPosition = poOwnerFormCenter) and (Owner is TCustomForm) then
CenterForm := TCustomForm(Owner);
if Assigned(CenterForm) then
begin
X := ((CenterForm.Width - Width) div 2) + CenterForm.Left;
Y := ((CenterForm.Height - Height) div 2) + CenterForm.Top;
end else
begin
{$IFDEF FIXUP_FORM_POPUP_POSITION}
CenterFormToPrimaryMonitorCenter(X, Y, Self);
{$ELSE}
X := (Screen.Width - Width) div 2;
Y := (Screen.Height - Height) div 2;
{$ENDIF}
end;
{$IFDEF FIXUP_FORM_POPUP_POSITION}
SnapFormToMonitorEdgeOnDemand(X, Y, Self);
SetBounds(X, Y, Width, Height);
//SetWindowToMonitor() will cause unexpected popup position change!
{$ELSE}
if X < Screen.DesktopLeft then
X := Screen.DesktopLeft;
if Y < Screen.DesktopTop then
Y := Screen.DesktopTop;
SetBounds(X, Y, Width, Height);
if Visible then SetWindowToMonitor;
{$ENDIF}
end
else if FPosition = poDesktopCenter then
// ...
end;

Conclusion

This issue is nothing. The motivation of this article [2] is to suggest you figure out the differences among DesktopRect, BoundsRect and WorkAreaRect. They are almost the same on one monitor environment. But they are totally different on dual monitors, especially when TaskBar is not at its default position.

References

  1. Related discuss at QualityCentral.
  2. The Chinese version of this article (on my blog @csdn)
 

Sunday, April 27, 2008

A not cool thing of TCoolBar

There is a demo project "coolstuf" in Delphi folder. It demonstrates how to build your own web browser with Delphi. Unfortunately it shows you how to re-procedure an issue of TCoolBar as well. "Just drag the (third) Links toolbar to top and then to bottom, repeat it for several times, you will see an extra area (roughly 40px) at bottom of TCoolBar." I call this as a "TCoolBar Bands' Position Problem". If you do not get it, please look at the snapshot.


Snapshot (Red rectangle is the extra area)

If you have read other posts in my blog, you might know, that I am very uncomfortable with such UI problems. So I did some investigation: This issue take place, when one or more bands have a fixed size. (TCoolBand.FixedSize is True) I suppose, that the height of this (fixed-size) band was not updated, when TCoolBar's height was changed. Actually, this is not the only one issue of REBAR controls. For instance: No chevron support, no whole dropdown button support, flickering problem, etc. (I will talk about them later in another post)

Solution

I have reported this issue to QualityCentral [1]. Not like other patches I have made, this time, the solution is not clean enough.

You have to:
  1. Copy ComCtrls.pas to your project folder,
  2. find and replace with the following code,
  3. and then recompile your project again.


procedure TCoolBar.CNNotify(var Message: TWMNotify);
//PATCH-BEGIN
var
I, FID: Integer;
RBBI: TReBarBandInfo;
//PATCH-END
begin
if (Message.NMHdr^.code = RBN_HEIGHTCHANGE) then
if IsAutoSized and (ComponentState * [csLoading, csDestroying] = [])
then begin
//PATCH-BEGIN
for I := 0 to Bands.Count - 1 do
if Bands[I].FixedSize then
begin
FillChar(RBBI, SizeOfReBarBandInfo, 0);
RBBI.cbSize := SizeOfReBarBandInfo;
RBBI.fMask := RBBIM_STYLE;
FID := Bands[I].FID;
Perform(RB_GETBANDINFO, FID and IDMask, Integer(@RBBI));
Perform(RB_SETBANDINFO, FID and IDMask, Integer(@RBBI));
end;
//PATCH-END

ReadBands;
BeginUpdate;
try
if AutoSize then AdjustSize;
finally
EndUpdate;
end;
end
else if IsBackgroundDirty then
Invalidate;
end;

Conclusion

As I mentioned before, REBAR controls (TCoolBar, TCoolBand, TToolBar and TToolButton) have not been implemented correctly as they should be. I did some experiments and made couple of patches. This article [2] is only the part one. There will be part two, part three...

To note that some patches must modify interface declaration and implementation both. Unfortunately Delphi cannot re-compile those modified units, when their interface declaration has been changed. The only one solution, so far as I known, is to create a copy of the whole REBAR control set and modify the new set. I will be appreciated, if someone can suggest any better ways.

References

  1. Related discuss at QualityCentral
  2. The Chinese version of this article (on my blog @csdn)