I have a Delphi program that needs to be run via Remote Desktop Services. What should I look out for that would stop it running properly ?
Andreas is correct in pin-pointing double-buffering. This is the most important aspect to consider that I am aware of.
As a mild counter point, I don't like double-buffering in general because it's very hard to get it right. Many components don't succeed. I'm thinking of VCL drop down list boxes which don't draw right under Windows Basic. There are others!
But some controls really do need double-buffering to avoid flicker, so what do you do? You want the benefit of double buffering when the user is locally connected, but you don't want to tax them with the network bandwidth when they are remote.
So, here's what I do:
procedure WMWTSSessionChange(var Message: TMessage); message WM_WTSSESSION_CHANGE;
procedure TBaseForm.WMWTSSessionChange(var Message: TMessage);
begin
case Message.WParam of
WTS_CONSOLE_DISCONNECT,WTS_REMOTE_DISCONNECT,
WTS_SESSION_LOCK,WTS_SESSION_LOGOFF:
SessionDisconnected;
WTS_CONSOLE_CONNECT,WTS_REMOTE_CONNECT,
WTS_SESSION_UNLOCK,WTS_SESSION_LOGON:
SessionConnected;
end;
inherited;
end;
function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): BOOL; stdcall; external 'Wtsapi32.dll';
function WTSUnRegisterSessionNotification(hWnd: HWND): BOOL; stdcall; external 'Wtsapi32.dll';
const
NOTIFY_FOR_THIS_SESSION = 0;
NOTIFY_FOR_ALL_SESSIONS = 1;
procedure TBaseForm.CreateWnd;
begin
inherited;
WTSRegisterSessionNotification(WindowHandle, NOTIFY_FOR_THIS_SESSION);
end;
procedure TBaseForm.DestroyWnd;
begin
WTSUnRegisterSessionNotification(WindowHandle);
inherited;
end;
All forms in my app descend from TBaseForm
and so inherit this behaviour. The SessionConnected
and SessionDisconnected
methods are virtual
so individual forms can take specific actions.
In particular, all my forms call UpdateDoubleBuffered
:
function InRemoteSession: Boolean;
begin
Result := Windows.GetSystemMetrics(SM_REMOTESESSION)<>0;
end;
class procedure TBaseForm.UpdateDoubleBuffered(Control: TWinControl);
var
DoubleBuffered: Boolean;
begin
if InRemoteSession then begin
//see The Old New Thing, Taxes: Remote Desktop Connection and painting
DoubleBuffered := False;
end else begin
DoubleBuffered := (Control is TCustomListView)
or (Control is TCustomStatusBar);
//TCustomListView flickers when updating without double buffering
//TCustomStatusBar has drawing infidelities without double buffering in my app
end;
Control.DoubleBuffered := DoubleBuffered;
end;
procedure TBaseForm.UpdateDoubleBuffered;
var
Control: TControl;
begin
for Control in ControlEnumerator(TWinControl) do begin
UpdateDoubleBuffered(TWinControl(Control));
end;
end;
ControlEnumerator
is an enumerator that walks the children of a component.
The Old New Thing reference is to an article entitled Taxes: Remote Desktop Connection and painting which was my inspiration for much of this code.
I'm sure that there are other issues relating to remote desktop, but double-buffering is certainly one of the more important ones.
NOTIFY_FOR_THIS_SESSION
. I'd forgotten about that part of the code and of course it doesn't make sense to listen for a message that you aren't going to receive until you've requested notification! Thanks for spotting this. –
Pignut Double buffering is one such thing.
Generally double-buffering is a great thing, and I use it all the time. See for instance my components (1), (2), and (3). They are all double-buffered, and therefore completely free from flickering (and easy to implement), but if you run this remotely, you have to send bitmaps and not GDI commands, so it might be rather slow (and uneconomical).
Alpha-transparency forms aren't well supported IME (either by the clients or the servers, depending on their versions).
You also have to worry about colors, most remote desktop operate in 16 bit colors, or even just 256 colors, if you have a clean modern UI, you may need to dumb it down into "ugly" mode so that everything is guaranteed to be readable.
In the same vein, beware of anti-aliased text, which is a must on any modern UI, but can result in unreadable blurred characters on low-color RDP.
There are can be other issues with notifications bubbles and other shell-based effects, so you'll probably want to handle them yourself, in regular forms, rather than rely on the Windows API functionality.
Graphically speaking, double-buffering can be a double-edged sword, in some cases (low-key graphics) it can be beneficial to turn it off, but if you have more advanced renderings (gradients, transparent bitmaps, scaled bitmaps, fancy fonts, anti-aliased lines etc.) you can get better looks and speed with double-buffering, as a slight lag can be better than interactive drawing with flickering.
Also some bitmaps can get through faster than others, the usual fast compressions used in RDP favour vertical gradients over horizontal gradients f.i.
Another issue will be with the files rights, if your application never had to run under a regular user account. Also special folders (like temp) and registry keys can have different (dynamic) locations, and restrictions. But if you're application as already been running under an restricted user account, you should already have everything covered.
If this is an enterprise-wide or web-delivered app run by hundreds or thousands of users, pay particular attention to color depth, gradients, and frivilous animations. Especially that last one. A 16x16 spinning "sync" icon won't eat too much bandwidth, but larger things may generate a lot of traffic. Gradients look crappy when the color level goes down, and they don't compress well. Color levels go down when network/system admins need to squeeze more out of the network. You may not be able to control it. These concerns would also apply to Citrix apps, which typically run without a full desktop. One extra consideration with that, is that users won't see the system tray notification area.
With Citrix, we have had issues with printers. Sometimes the connection will use the printers the client has defined on their machine, sometimes the printers are from other users' sessions, and other times the default printer is at a completely different location inaccessible to our user.
I had many problems with things flickering so much that they were un-usable through remote desktop. This was because I was updating things like Captions or Status panels, many times per second. I changed all my update-UI code to check if a caption had actually changed:
if Str<>WeirdControl.Property1 then
WeirdControl.Property1 := Str; // minimize number of invalidates in case the control doesn't have it.
Along with other things mentioned in other answers, this made my applications usable again in remote desktop situations.
© 2022 - 2024 — McMap. All rights reserved.