Thoughts from Dan Miser RSS 2.0
 Friday, October 15, 2004
If you've ever developed an ActiveForm, then you probably have run into the scenario where you go to a standard control in your form (e.g. an Edit control), select the text, and then press Ctrl+C to copy the text. However, I'm willing to bet that your next reaction was stunned amazement when you realized that the text was not actually copied to the clipboard. To make matters even more maddening, if you use Ctrl+X or Ctrl+V in that very same edit control, things work as you would expect.

I had to fix this problem recently, and it is awfully strange. Pressing Ctrl+X and Ctrl+V makes the TActiveFormControl.TranslateAccelerator method return S_FALSE. Pressing Ctrl+C, the return is S_OK. So in order to fix this, I want to return S_FALSE when Ctrl+C is pressed. I do this by creating a new TActiveXControlClass that overrides the TranslateAccelerator method and returns S_FALSE when needed. The only other thing that needs to be done is to use the new class in your TActiveFormFactory.Create call so that we can get calls to our TranslateAccelerator method and do our own custom stuff.

One caveat: Things are working quite well in IE6. I'm not sure what will happen in other ActiveX containers. Use at your own risk, or at least test in whatever container you're deploying to first!

Here's the code. Please let me know what you think. If you see any improvements that can be made, I'd love to hear about those, too. But at least, for now, my customers are happy again.


type
 TTranslateAcceleratorFormControl = class(TActiveFormControl, IOleInPlaceActiveObject)
  function TranslateAccelerator(var msg: TMsg): HResult; stdcall;
 end;

{ TTranslateAcceleratorFormControl }
{ 
When pressing Ctrl+C, the inherited method returns S_OK meaning that the container thinks
it did what it needed to, and therefore, the message is consumed. When pressing Ctrl+X or
Ctrl+V, the return from the inherited method is S_FALSE.

Therefore, we will always call the inherited method to allow for default processing. If the
return is S_OK, and the message is WM_KEYDOWN, and we have pressed Ctrl+C, we override
the result and return S_FALSE instead.
}
function TTranslateAcceleratorFormControl.TranslateAccelerator(var msg: TMsg): HResult;
begin
  Result := inherited TranslateAccelerator(msg);

  if Result = S_OK then  
  begin
    if (msg.message = WM_KEYDOWN) and (GetKeyState(VK_CONTROL) < 0) and (msg.wParam = 67) then
      Result := S_FALSE;
  end;
end;

initialization
  TActiveFormFactory.Create(
    ComServer,
    //TActiveFormControl,             // standard Delphi-generated class
    TTranslateAcceleratorFormControl, // our replacement class
    TAFActionTest,
    Class_AFActionTest,
    1,
    '',
    OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
    tmApartment);
end.
Friday, October 15, 2004 12:56:00 PM (Central Standard Time, UTC-06:00)  #    Comments [1] -
Delphi
Falafel has assembled a team whose excellence is hard to surpass. Now they're putting on a conference. The line up of speakers and cost makes me wonder how it's possible, but who am I to question it. :-)

I hope everyone doesn't rush out there and sign up right away and shut me out of a spot. I'm trying to work some things out so that I can get there.

Friday, October 15, 2004 8:26:00 AM (Central Standard Time, UTC-06:00)  #    Comments [1] -

 Thursday, October 14, 2004
I was in the process of solving a problem that required me to do some message spying. I haven't used WinSight in several years, but the last time I used it, it was sufficient for my needs. Well, either the past is causing me to remember the experience more fondly (doubtful), or the environments have changed enough over the past few years to cause WinSight to not be as good any more (more likely). The current problem boils down to several windows that were not properly identified, and as a result, I could not trace messages, see window details, or simply navigate through the window hierarchy.

I installed Spy++ from VS.NET, and it worked rather well. I found what I needed with minimal fuss. Spy++ isn't a bad application, overall.

I did do some digging and found a free message spy application. I haven't tried it, but it looks promising and is billed to be better than Spy++. Check out Winspector and try it for yourself.

Thursday, October 14, 2004 9:27:00 AM (Central Standard Time, UTC-06:00)  #    Comments [2] -

 Wednesday, October 13, 2004
I've written some about this interface, and how it allows you to grab data from an HTML page and use that data inside an ActiveX control. In a nutshell, your HTML will have an OBJECT tag for the ActiveX control and - optionally - some PARAM tags inside the OBJECT tag. One thing to keep in mind is that the IPersistPropertyBag methods will only fire if you actually define at least one PARAM tag. Without a PARAM tag, the code will not execute.

The moral of the story is: if you have some kind of initialization code in your IPersistPropertyBag methods, be doubly sure that you define a PARAM tag!

Which reminds me, now that Delphi Informant is no longer in business, I'll see what I can do to get my articles published with them posted on my web site. I'll post an entry here when I can do that.

Wednesday, October 13, 2004 10:49:00 AM (Central Standard Time, UTC-06:00)  #    Comments [2] -
Delphi
In SysUtils, there is a handy function (CheckWin32Version) to check that you are running at least a certain version of Windows. Unfortunately, it is wrong. For example, let's say you want to make sure you are running Win2000 or greater. The following code should do just that:

  if CheckWin32Version(5) then
    CallMyWin2000OrGreaterFunctionHere;
When running on XP, the major version will be 5, and the minor version will be 1. These are stored in writable constants in SysUtils. Inside CheckWin32Version, the check for AMinor is backwards, so your results will be wrong.

I am now using this function instead and everything works fine:


function FixedCheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean;
begin
  Result := (AMajor < Win32MajorVersion) or
            ((AMajor = Win32MajorVersion) and
             (AMinor <= Win32MinorVersion));
end;

Just a side-note: This was fixed in Delphi 7 and above. Of course, if you're using D6, you'll need to use this.

Wednesday, October 13, 2004 10:38:00 AM (Central Standard Time, UTC-06:00)  #    Comments [4] -
Delphi
 Friday, September 24, 2004
I'm a little partial to Borland DB technologies, and especially BDP. ;-) However, I think this demo shows some of the power that BDP has in the upcoming version of Delphi. Check out the BDNtv episode presented by Jason Vokes and see what you think.

I saw many other properties and methods in this demo that weren't touched on. All in all, BDP looks incredibly flexible in Diamondback - especially when either doing multi-tier apps, or using data from heterogenous sources.

Friday, September 24, 2004 8:51:00 AM (Central Standard Time, UTC-06:00)  #    Comments [1] -
Delphi
 Thursday, September 23, 2004
By default, COM Servers register themselves in HKEY_CLASSES_ROOT (HKCR). The actual registry entries will get written to HKEY_LOCAL_MACHINE (HKLM) in most cases. HKCR is an alias to HKEY_LOCAL_MACHINE in older versions of Windows, but starting in Windows 2000 and later, HKCR is a merged view of HKLM and HKEY_CURRENT_USER (HKCU). I needed a way to register my COM server into HKCU so that locked down machines that don't have access to HKLM could still run properly. For some background information on HKCR, check out MSDN.

I came across the function RegOverridePredefKey, and it looked like it would do the job by allowing me to hook into a root HKEY and write to a completely different HKEY. So I started looking in the Delphi source code, and noticed that this function was not imported. The following code snippet shows the procedure that I ended up writing to have anything written to HKCR be written to HKCU instead.


function RegOverridePredefKey(hKey: HKEY; hNewKey: HKEY): Longint; stdcall; 
  external advapi32 name 'RegOverridePredefKey';

procedure OverrideRegistryKey(Register: boolean);
var
  HKCU: HKEY;
  ret: integer;
begin
  if Register then
  begin
    RegOpenKeyEx(HKEY_CURRENT_USER, 'Software\Classes', 0, HEY_ALL_ACCESS, HKCU);
    try
      ret := RegOverridePredefKey(HKEY_CLASSES_ROOT, HKCU);
      if ret <> ERROR_SUCCESS  then
        ShowMessage('Error overriding HKCU:' + #13#10 + SysErrorMessage(ret));
    finally
      RegCloseKey(HKCU);
    end;
  end
  else
    RegOverridePredefKey(HKEY_CLASSES_ROOT, 0);
end;
So that left me with the question "Where do I call this?". My first thought was to hook into the UpdateRegistry method and call my OverrideRegistry procedure there. Unfortunately, by the time UpdateRegistry is called in my Remote DataModule (RDM), several other registration calls have already finished. This left me with some things in HKLM and some in HKCU. Then I remembered that the COM registration occurs when Application.Initialize is called in the dpr file. The following is the code I use in my dpr to write to the right place in the registry.

  OverrideRegistryKey(true);
  try
    Application.Initialize;
  finally
    OverrideRegistryKey(false);
  end;
After doing this, I checked in regedit and verified that everything was registered in HKCU, and it was there. Since HKCR shows the merge of HKLM and HKCU, it was also present in HKCR. This means that COM calls will find the app server. Running a quick test showed me that everything worked as I expected.

In my production code, I'll also put in a quick check to make sure that we're running on Windows 2000 or greater. Since we run both as a Server and a Service, I'll put this code in a conditional define, since there is no concept of HKCU in a service.

Note: I also failed to find the header for RegOpenUserClassesRoot in Delphi. If you find that you need this function, you'll need to import the function yourself.

Thursday, September 23, 2004 3:48:00 PM (Central Standard Time, UTC-06:00)  #    Comments [0] -
Delphi
 Thursday, September 16, 2004
Joe White took some notes on NDataStore. I will be keeping a very, very close eye on this DB. I have used JDataStore in past lives, and it was an extreme pleasure to use it. Steve Shaugnessy really knows how to eek performance out of a DBMS.
Thursday, September 16, 2004 9:30:00 AM (Central Standard Time, UTC-06:00)  #    Comments [3] -
ADO.NET
 Wednesday, September 15, 2004
Take a look at this sneak peek of DiamondBack presented by John Kaster. Major things I noticed just in this 14 minute demo were:
  • Language bundling announement (C#, Delphi, Win32)
  • for..in syntax (similar to C# foreach syntax)
  • A new start page (to replace DelphiDirect, most likely)
  • Refactoring support (available in all languages)
  • Error Insight
  • Help Insight
  • History view (including smart diff engine. Similar to JBuilder's feature of the same name - very nice)
  • Tight StarTeam integration
  • Free floating form designer (a la Delphi 7 and below)
  • Delphi Win32 was enhanced to support many extensions created in Delphi .NET
Wow!! It seems pretty full-featured, and I didn't even see all of the features first-hand at BorCon. I can't wait to get my hands on it. Well done, John and Borland.
Wednesday, September 15, 2004 1:05:00 PM (Central Standard Time, UTC-06:00)  #    Comments [1] -
Delphi
 Tuesday, September 14, 2004
Yup, it's time to talk about ActiveForms again. :-)

If you're using ActiveForms, then you need to know about an ugly little secret. If you use MyDelphiForm.ActiveControl := Edit1, then you will get an exception stating 'Cannot focus a disabled or invisible window'. The reason for this is due to the implementation of TCustomForm.SetActiveControl. This setter method is called when you try to set ActiveControl on a form. The first thing it does is this:


    if not ((Control = nil) or (Control <> Self) and
      (GetParentForm(Control) = Self) and ((csLoading in ComponentState) or
        Control.CanFocus)) then
      raise EInvalidOperation.Create(SCannotFocus);
Note the part where it is trying to assert that the root ParentForm for the Control you are trying to set focus to is the same as the current form. This works fine in regular windows applications, but unfortunately in ActiveForms, the root ParentForm is the actual ActiveForm. Most people use that form as nothing more than a container to then host their forms inside. I wrote about this technique in a previous Delphi Informant, and it has been posted plenty of places on the Internet as well (e.g. Conrad Hermann had the first mention of this that I can remember).

The workaround is to use code like this to set the ActiveControl:


var
  ParentForm: TCustomForm;
begin
  ParentForm := GetParentForm(Self);
  ParentForm.ActiveControl := Edit1;
Tuesday, September 14, 2004 9:37:00 AM (Central Standard Time, UTC-06:00)  #    Comments [1] -
Delphi
Navigation
Archive
<October 2004>
SunMonTueWedThuFriSat
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2008
Dan Miser
Sign In
Statistics
Total Posts: 306
This Year: 21
This Month: 0
This Week: 0
Comments: 604
All Content © 2008, Dan Miser
DasBlog theme 'Business' created by Christoph De Baene (delarou)