EM_SETSEL swaps parameters - c++

I use EM_SETSEL message to select text in edit control. I need to select some text from the end to the middle, so that caret position is in the middle of the text.
MSDN documentation states the following:
The start value can be greater than the end value. The lower of the two values specifies the character position of the first character in the selection. The higher value specifies the position of the first character beyond the selection.
The start value is the anchor point of the selection, and the end value is the active end. If the user uses the SHIFT key to adjust the size of the selection, the active end can move but the anchor point remains the same.
But it seems that lesser value always becomes an anchor, e.g. I cannot achieve the desired behaviour.
Code sample (where "parent" is CWnd*):
TRACE("EM_SETSEL(%d, %d)\n", pos1, pos2);
parent->SendMessage(EM_SETSEL, pos1, pos2);
parent->SendMessage(EM_GETSEL, (WPARAM)&pos1, (LPARAM)&pos2);
TRACE("EM_GETSEL(%d, %d)\n", pos1, pos2);
produces the output:
EM_SETSEL(5, 1)
EM_GETSEL(1, 5)
Is there another way to get the desired selection?

Regarding EM_GETSEL/EM_SETSEL:
EM_GETSEL retrieves left/right positions
EM_SETSEL sets anchor/active positions
EM_SETSEL uses anchor/active positions, allowing you to easily place the caret at the left/right of the selection, so I'm not sure why a kludge was used in the other answer.
EM_GETSEL is the awkward window message, for which a kludge is necessary. This kludge temporarily changes the selection to 0 characters, in order to retrieve the active position, however, when I've used it I haven't seen any visible change.
To retrieve anchor/active positions:
use EM_GETSEL to retrieve the left/right positions
use EM_SETSEL to temporarily set the selection to 0 characters, leaving the caret at the active position
use EM_GETSEL to retrieve the active position
use EM_SETSEL to restore the original selection
Some example AutoHotkey code for setting the selection:
q:: ;Notepad - set active position (caret) at right
PostMessage, 0xB1, 5, 10, Edit1, A ;EM_SETSEL := 0xB1
return
w:: ;Notepad - set active position (caret) at left
PostMessage, 0xB1, 10, 5, Edit1, A ;EM_SETSEL := 0xB1
return
Some example AutoHotkey functions for getting/setting the selection:
JEE_EditGetRange(hCtl, ByRef vPos1, ByRef vPos2)
{
VarSetCapacity(vPos1, 4), VarSetCapacity(vPos2, 4)
SendMessage, 0xB0, % &vPos1, % &vPos2,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0 ;(left, right)
vPos1 := NumGet(&vPos1, 0, "UInt"), vPos2 := NumGet(&vPos2, 0, "UInt")
}
;==================================================
JEE_EditSetRange(hCtl, vPos1, vPos2, vDoScroll:=0)
{
SendMessage, 0xB1, % vPos1, % vPos2,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1 ;(anchor, active)
if vDoScroll
SendMessage, 0xB7, 0, 0,, % "ahk_id " hCtl ;EM_SCROLLCARET := 0xB7
}
;==================================================
;note: although this involves deselecting and selecting it seems to happen invisibly
JEE_EditGetRangeAnchorActive(hCtl, ByRef vPos1, ByRef vPos2)
{
;get selection
VarSetCapacity(vPos1, 4), VarSetCapacity(vPos2, 4)
SendMessage, 0xB0, % &vPos1, % &vPos2,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0
vPos1 := NumGet(&vPos1, 0, "UInt"), vPos2 := NumGet(&vPos2, 0, "UInt")
if (vPos1 = vPos2)
return
vPos1X := vPos1, vPos2X := vPos2
;set selection to 0 characters and get active position
SendMessage, 0xB1, -1, 0,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1
VarSetCapacity(vPos2, 4)
SendMessage, 0xB0, % &vPos2, 0,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0
vPos2 := NumGet(&vPos2, 0, "UInt")
;restore selection
vPos1 := (vPos2 = vPos2X) ? vPos1X : vPos2X
SendMessage, 0xB1, % vPos1, % vPos2,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1 ;(anchor, active)
}
LINKS:
The functions above that I originally posted at the AutoHotkey forums:
GUI COMMANDS: COMPLETE RETHINK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=25893&p=138292#p138292

#vafylec's answer is good. I've just rewritten it here Delphi, which is a little easier to parse and translate to other languages than AHK.
Basically, EM_SETSEL allows you to set the anchor at either end of the range, but EM_GETSEL only ever returns the first and last character, losing that information. With a little kludge, you can work around this:
procedure GetEditSelection(Handle: THandle; var Anchor, Start, Finish: Integer);
begin
SendMessage(Handle, EM_GETSEL, NativeUInt(#Start), NativeUInt(#Finish));
SendMessage(Handle, EM_SETSEL, -1, 0);
SendMessage(Handle, EM_GETSEL, NativeUInt(#Anchor), 0);
if Anchor = Start then
SendMessage(Handle, EM_SETSEL, Finish, Start)
else
SendMessage(Handle, EM_SETSEL, Start, Finish);
end;

Related

FireMonkey drawing with tPath does not fill the shape

I want to use a TPathData to draw shapes and fill them with an arbitrary color. I'm using the following code, at Button1Click, wich I extracted from a sample at Embarcadero documentation:
procedure TformPathDrawing.Button1Click(Sender: TObject);
var path: TPathData;
begin
Image1.Bitmap.Canvas.Fill.Color := TAlphaColorRec.Blue;
path := TPathData.Create;
path.Data := 'M 01,00 L 02,01 L 01,02 L 00,01 L 01,00';
Image1.Bitmap.Clear ($FFFFFF);
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillPath (path, 200);
Image1.Bitmap.Canvas.EndScene;
end;
When this code is executed, as expected, a romboid is rendered, but it is not filled up with the color set in the first command. Anyone knows what is wrong? Thanks.
Here is right code. You can find another details in FMX.Objects.pas procedure TCustomPath.UpdateCurrent and other; or just Debug TPath visual component.
{aPath - is vector path of image;
aStretch - how to draw vector data - true - stretch draw, false = fit to bitmap
aBitmap - must be created. In bitmap you should specify:
Width, Height, Bitmap.Canvas.Fill.Color, Bitmap.Canvas.Stroke.Color
and, if you need, another drawing stuff like Gradient, stroke thikness,
background texture) }
procedure TForm1.DrawVectorPath(const aPath: string; aBitmap: TBitmap; aStretch: boolean);
var
vPath: TPathData;
R: TRectF;
begin
Assert(aBitmap <> nil);
vPath := TPathData.Create;
try
vPath.Data := aPath;
aBitmap.Clear($FFFFFF);
if aStretch then
begin
R := vPath.GetBounds;
vPath.Translate(-R.Left, -R.Top);
vPath.Scale(aBitmap.Width / R.Width, aBitmap.Height / R.Height);
end
else // Fit image
begin
R := TRect.Create(0, 0, aBitmap.Width, aBitmap.Height);
vPath.FitToRect(R);
end;
aBitmap.Canvas.BeginScene;
aBitmap.Canvas.FillPath(vPath, 1);
aBitmap.Canvas.DrawPath(vPath, 1);
aBitmap.Canvas.EndScene;
finally
vPath.Free;
end;
end;
Because comments can't have code (at least not formatted). Here is my code, that works.
procedure TForm7.RadioButton6Click(Sender: TObject);
var path: TPathData;
begin
Image1.Bitmap.Canvas.Fill.Color := TAlphaColorRec.Blue;
Image1.Bitmap.Canvas.Stroke.Color := TAlphaColorRec.black;
path := TPathData.Create;
try
path.Data := 'M 01,00 L 20,01 L 10,20 L 00,10 L 01,00';
Image1.Bitmap.Clear ($FFFFFF);
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillPath (path, 200);
Image1.Bitmap.Canvas.EndScene;
finally
path.Free;
end;
end;
Note that the stroke color is set as well. That's the only difference I can make out here.
Follows code that works (in my case):
procedure TformPathDrawing.Button1Click(Sender: TObject);
begin
Image1.Bitmap.Canvas.Fill.Color := TAlphaColorRec.Blue;
path.Clear;
path.Data := 'M 01,00 L 02,01 L 01,02 L 00,01 L 01,00';
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillPath (path, 1);
Image1.Bitmap.Canvas.EndScene;
end;
In my code, variable path is created outside the painting code. Since path.Data; is additive, it is mandatory, before adding the current path.Data;, to place a path.Clear; statement, to clean out whatever remains into the point array. Hope this help other people.

BeginPath Textout EndPath draws inverted text

here is the code that I have in OnPaint event of my form:
int elementCount;
String tStr = L"15:00";
::BeginPath(Canvas->Handle);
::TextOut(Canvas->Handle, 5, 5, tStr.c_str(), tStr.Length());
::EndPath(Canvas->Handle);
elementCount = ::GetPath(Canvas->Handle, NULL, NULL, 0);
Canvas->Brush->Color = clBlue;
Canvas->Pen->Color = clYellow;
Canvas->Pen->Width = 4;
if(0 < elementCount)
{
boost::scoped_array<TPoint> mPoints(new TPoint[elementCount]);
boost::scoped_array<BYTE> mTypes(new BYTE[elementCount]);
::GetPath(Canvas->Handle, mPoints.get(), mTypes.get(), elementCount);
::FillPath(Canvas->Handle);
::PolyDraw(Canvas->Handle, mPoints.get(), mTypes.get(), elementCount);
}
else
::StrokeAndFillPath(Canvas->Handle);
but here is what I get on the form:
as you can see the text comes out inverted (the text has to be blue and background gray but it is the other way around and the yellow line is around the background instead of text). Does anyone know how I can fix this?
I am using C++ Builder 10 Seattle but if anyone knows that Delphi or pure C++ trick, I can work with that as well.
Thank you
This is explained in TextOut's documentation:
When the TextOut function is placed inside a path bracket, the
system generates a path for the TrueType text that includes each
character plus its character box. The region generated is the
character box minus the text, rather than the text itself. You can
obtain the region enclosed by the outline of the TrueType text by
setting the background mode to transparent before placing the
TextOut function in the path bracket. Following is sample code that demonstrates this procedure.
The below is a Delphi adaption of the mentioned sample code and your snippet, draws yellow outlined blue text:
procedure TForm1.FormPaint(Sender: TObject);
var
elementCount: Integer;
mPoints: array of TPoint;
mTypes: array of Byte;
const
tStr = '15:00';
begin
BeginPath(Canvas.Handle);
Canvas.Brush.Style := bsClear;
TextOut(Canvas.Handle, 5, 5, PChar(tStr), Length(tStr));
EndPath(Canvas.Handle);
Canvas.Brush.Color := clBlue;
Canvas.Pen.Color := clYellow;
Canvas.Pen.Width := 4;
elementCount := GetPath(Canvas.Handle, Pointer(nil)^, Pointer(nil)^, 0);
if elementCount > 0 then begin
SetLength(mPoints, elementCount);
SetLength(mTypes, elementCount);
GetPath(Canvas.Handle, mPoints[0], mTypes[0], elementCount);
Canvas.Brush.Style := bsSolid;
SelectClipPath(Canvas.Handle, RGN_AND);
Canvas.FillRect(ClientRect);
SelectClipRgn(Canvas.Handle, 0);
PolyDraw(Canvas.Handle, mPoints[0], mTypes[0], elementCount);
end else
StrokeAndFillPath(Canvas.Handle);
end;

AHK if/else of output from Custom Message Box Function

I've been trying to create a script that displays message boxes conditioned on the output from another customized message box with more than 3 options.
The custom functions are taken form this thread http://www.autohotkey.com/board/topic/29570-function-custom-msgbox-custom-buttons/page-2
I've tried both the CMsgbox and the MsgBoxV2 functions, running into the same problem. The Message "IT WORKED!" testing with if(condition) will always appear regardless of whether pressing any of the Sym|&Go|&C options, and the message "worked" will never appear regardless of actually pressing C, In other words no specificity or sensitivity regarding variable testing.
!o::
var := MsgBoxV2("Question11","Hi, what's up??","Sym|Go|C","24")
Msgbox 4,, You Pressed %var%
if (%var% = C)
{
Msgbox 4,, IT WORKED!
}
IfEqual, %var%,C
{
Msgbox 4,, worked
}
return
!e::
var := CMsgbox( "s", "d", "*&Sym|&Go|&C","",1 )
Msgbox 4,, You Pressed %var%
if (%var% = C)
{
Msgbox 4,, IT WORKED!
}
IfEqual, %var%,C
{
Msgbox 4,, worked
}
return
I don't know if this is because I've misunderstood the if/Else testing in ahk, or if the output from these custom functions cannot be tested, or both.
One solution would be to figure out what type of variable %var% is, but I've not been able to find a way to figure that out either.
Thanks for reading and hope you guys can help.
here are the custom message functions for testing.
MsgBoxV2(Title="",Text="",Buttons="",IconPath="",Timeout="",Font="",Schriftart="Arial",Colors="||",WindowStyles="",GuiNr = "")
{
Static Stat2,Stat1
Col1:="0xFFFFFF" ,Col2:="0x000000",Col3:="0x000000", Color1:="", Color2:="", Color3:=""
Loop, Parse, Colors,`|
Color%A_Index% := (A_Loopfield = "") ? Col%A_Index% : A_Loopfield
Loop 3
Color%A_Index% := (Color%A_Index% = "") ? Col%A_Index% : Color%A_Index%
if instr(WindowStyles,"+altsubmit")
{
AltSub := "1"
Stringreplace,WindowStyles,WindowStyles,`+altsubmit
}
Gui, Color, %Color1%,%Color1%
Gui, Font, s9
Gui, Font, %Font%, %Schriftart%
X := 20 ,Y := 20
ifexist, %IconPath%
{
Gui, Add, Picture, x20 y20 w32 h32, %IconPath%
X := 70 ,Y := 30
} else
if IconPath is integer
{
Gui, Add, Picture, x20 y20 icon%IconPath% w32 h32, %A_WinDir%\system32\shell32.dll
X := 70 ,Y := 30
}
Gui, Add, Text, x%X% y%Y% c%Color2% vStat2, %Text%
GuicontrolGet, Stat2, Pos
X2 = 10
Y2 := (Stat2Y + Stat2H < 52) ? 82 : Stat2Y + Stat2H + 30
HMax = 0
Gui, Add, Text, vStat1 +border -background
Loop, Parse, Buttons,|,`|
{
Gui, Add, Button, x%X2% w100 Y%Y2% gExButton , %A_Loopfield%
ButT%A_Index% := A_Loopfield
Guicontrolget, Button%A_Index%,Pos
if (HMax < Button%A_Index%H)
HMax := Button%A_Index%H
ABut := A_Index
X2 += 110
}
Loop %ABut%
Guicontrol, Move, Button%A_Index%,h%HMax%
Gui, %WindowStyles%
Gui, Show, Autosize Center,%Title%
Guicontrol, Move, Stat1, % "X-1 Y" Y2 - 10 " W" 1400 " h" 41 + HMax
Guicontrol, -Background +hidden, Stat1
Guicontrol, show, Stat1
Gui, +LastFound
WinGet, G_id
if Timeout !=
if Timeout is integer
settimer, Timeout, %Timeout%
Winwait, ahk_id %G_id%
retval = 0
while winexist("ahk_id " G_id)
sleep 100
if !AltSub
return ButT%retval%
else
return retval
Timeout:
if Timeout =
return
GuiClose:
Gui, destroy
return
ExButton:
MouseGetPos,,,, Control
Stringreplace,retval,Control,Button
Gui, destroy
return
}
;-------------------------------------------------------------------------------
; Custom Msgbox
; Filename: cmsgbox.ahk
; Author : Danny Ben Shitrit (aka Icarus)
;-------------------------------------------------------------------------------
; Copy this script or include it in your script (without the tester on top).
;
; Usage:
; Answer := CMsgBox( title, text, buttons, icon="", owner=0 )
; Where:
; title = The title of the message box.
; text = The text to display.
; buttons = Pipe-separated list of buttons. Putting an asterisk in front of
; a button will make it the default.
; icon = If blank, we will use an info icon.
; If a number, we will take this icon from Shell32.dll
; If a letter ("I", "E" or "Q") we will use some predefined icons
; from Shell32.dll (Info, Error or Question).
; owner = If 0, this will be a standalone dialog. If you want this dialog
; to be owned by another GUI, place its number here.
;
;-------------------------------------------------------------------------------
CMsgBox( title, text, buttons, icon="", owner=0 ) {
Global _CMsg_Result
GuiID := 9 ; If you change, also change the subroutines below
StringSplit Button, buttons, |
If( owner <> 0 ) {
Gui %owner%:+Disabled
Gui %GuiID%:+Owner%owner%
}
Gui %GuiID%:+Toolwindow +AlwaysOnTop
MyIcon := ( icon = "I" ) or ( icon = "" ) ? 222 : icon = "Q" ? 24 : icon = "E" ? 110 : icon
Gui %GuiID%:Add, Picture, Icon%MyIcon% , Shell32.dll
Gui %GuiID%:Add, Text, x+12 yp w180 r8 section , %text%
Loop %Button0%
Gui %GuiID%:Add, Button, % ( A_Index=1 ? "x+12 ys " : "xp y+3 " ) . ( InStr( Button%A_Index%, "*" ) ? "Default " : " " ) . "w100 gCMsgButton", % RegExReplace( Button%A_Index%, "\*" )
Gui %GuiID%:Show,,%title%
Loop
If( _CMsg_Result )
Break
If( owner <> 0 )
Gui %owner%:-Disabled
Gui %GuiID%:Destroy
Result := _CMsg_Result
_CMsg_Result := ""
Return Result
}
9GuiEscape:
9GuiClose:
_CMsg_Result := "Close"
Return
CMsgButton:
StringReplace _CMsg_Result, A_GuiControl, &,, All
Return
The answer lies in syntax of your code. I highly suggest that you read the Tutorial in the Docs fully! It's really well written.
What immediately popped out to me is that your Variables in your If Expressions are surrounded by percents, which is wrong.
Here is a link to the section in the Tutorial that you need to read in order to fix your code.
Try this:
!o::
var := MsgBoxV2("Question11","Hi, what's up??","Sym|Go|C","24")
Msgbox 4,, You Pressed %var%
if (var = "C")
{
Msgbox 4,, IT WORKED!
}
!e::
var := CMsgbox( "s", "d", "*&Sym|&Go|&C","",1 )
Msgbox 4,, You Pressed %var%
If (var = "C")
{
Msgbox 4,, worked
}
return
The IfEqual command is pretty much deprecated and while it's still included in the latest releases, it's basically bad form to use since the If expression much easier to read. Also plain text excluding numbers in an expression should be surrounded by quotes. Hope this helps.

Converting LTR to RTL?

As you know many ui components and dev tools doesn't support rtl , we can call it flipping text , cause result is same example :
LTR
سلام salam متن راهنما word
RTL
word متن راهنما salam سلام
is there anyway to convert this LTR to RTL , i don't have any idea and language doesn't matter
Actually i am seeking for a solution to get this done in RAD Studio Firemonkey Application , as you may know firemonkey apps doesn't support rtl it's in roadmap of rad studio but not implemented yet
Under Windows, you can do that via the UniScribe API.
I've used this to convert Unicode text into set of glyphs, for our Open Source PDF writer.
You have source code sample in SynPdf.pas unit. See the TPdfWrite.AddUnicodeHexTextUniScribe method:
function TPdfWrite.AddUnicodeHexTextUniScribe(PW: PWideChar;
WinAnsiTTF: TPdfFontTrueType; NextLine: boolean; Canvas: TPdfCanvas): boolean;
var L, i,j: integer;
res: HRESULT;
max, count, numSp: integer;
Sp: PScriptPropertiesArray;
W: PWideChar;
items: array of TScriptItem;
level: array of byte;
VisualToLogical: array of integer;
psc: pointer; // opaque Uniscribe font metric cache
complex,R2L: boolean;
complexs: array of byte;
glyphs: array of TScriptVisAttr;
glyphsCount: integer;
OutGlyphs, LogClust: array of word;
procedure Append(i: Integer);
// local procedure used to add glyphs from items[i] to the PDF content stream
var L: integer;
W: PWideChar;
procedure DefaultAppend;
var tmpU: array of WideChar;
begin
SetLength(tmpU,L+1); // we need the text to be ending with #0
move(W^,tmpU[0],L*2);
AddUnicodeHexTextNoUniScribe(pointer(tmpU),WinAnsiTTF,false,Canvas);
end;
begin
L := items[i+1].iCharPos-items[i].iCharPos; // length of this shapeable item
if L=0 then
exit; // nothing to append
W := PW+items[i].iCharPos;
if not GetBit(complexs[0],i) then begin
// not complex items are rendered as fast as possible
DefaultAppend;
exit;
end;
res := ScriptShape(0,psc,W,L,max,#items[i].a,
pointer(OutGlyphs),pointer(LogClust),pointer(glyphs),glyphsCount);
case res of
E_OUTOFMEMORY: begin // max was not big enough (should never happen)
DefaultAppend;
exit;
end;
E_PENDING, USP_E_SCRIPT_NOT_IN_FONT: begin // need HDC and a selected font object
res := ScriptShape(Canvas.FDoc.GetDCWithFont(WinAnsiTTF),
psc,W,L,max,#items[i].a,
pointer(OutGlyphs),pointer(LogClust),pointer(glyphs),glyphsCount);
if res<>0 then begin // we won't change font if necessary, sorry
// we shall implement the complex technic as stated by
// http://msdn.microsoft.com/en-us/library/dd374105(v=VS.85).aspx
DefaultAppend;
exit;
end;
end;
0: ; // success -> will add glyphs just below
else exit;
end;
// add glyphs to the PDF content
// (NextLine has already been handled: not needed here)
AddGlyphs(pointer(OutGlyphs),glyphsCount,Canvas);
end;
begin
result := false; // on UniScribe error, handle as Unicode
// 1. Breaks a Unicode string into individually shapeable items
L := StrLenW(PW)+1; // include last #0
max := L+2; // should be big enough
SetLength(items,max);
count := 0;
if ScriptItemize(PW,L,max,nil,nil,pointer(items),count)<>0 then
exit; // error trying processing Glyph Shaping -> fast return
// 2. guess if requiring glyph shaping or layout
SetLength(complexs,(count shr 3)+1);
ScriptGetProperties(sP,numSp);
complex := false;
R2L := false;
for i := 0 to Count-2 do // don't need Count-1 = Terminator
if fComplex in sP^[items[i].a.eScript and (1 shl 10-1)]^.fFlags then begin
complex := true;
SetBit(complexs[0],i);
end else
if fRTL in items[i].a.fFlags then
R2L := true;
if not complex then begin
// no glyph shaping -> fast append as normal Unicode Text
if R2L then begin
// handle Right To Left but not Complex text
W := pointer(items); // there is enough temp space in items[]
W[L] := #0;
dec(L);
for i := 0 to L do
W[i] := PW[L-i];
AddUnicodeHexTextNoUniScribe(W,WinAnsiTTF,NextLine,Canvas);
result := true; // mark handled here
end;
exit;
end;
// 3. get Visual Order, i.e. how to render the content from left to right
SetLength(level,count);
for i := 0 to Count-1 do
level[i] := items[i].a.s.uBidiLevel;
SetLength(VisualToLogical,count);
if ScriptLayout(Count,pointer(level),pointer(VisualToLogical),nil)<>0 then
exit;
// 4. now we have enough information to start drawing
result := true;
if NextLine then
Canvas.MoveToNextLine; // manual NextLine handling
// 5. add glyphs for all shapeable items
max := (L*3)shr 1+32; // should be big enough - allocate only once
SetLength(glyphs,max);
SetLength(OutGlyphs,max);
SetLength(LogClust,max);
psc := nil; // cached for the same character style used
if Canvas.RightToLeftText then
// append from right to left visual order
for j := Count-2 downto 0 do // Count-2: ignore last ending item
Append(VisualToLogical[j]) else
// append from left to right visual order
for j := 0 to Count-2 do // Count-2: ignore last ending item
Append(VisualToLogical[j]);
end;
Of course, this is under Windows only. So it won't work on Mac OS X. You'll have to use another library under Mac OS X...
It's complicated. If you want to do it correctly, you must use the Bidi Library from the International Components for Unicode.
If you use MFC, here is how to set both righting direction and alignment. Assuming your CEdit control is named m_TextEdit:
void MyDialog::SetLangDirection(bool RTL)
{
DWORD w_dwStyle;
w_dwStyle = GetWindowLong(m_TextEdit.GetSafeHwnd(), GWL_EXSTYLE);
if (RTL)
{
w_dwStyle -= WS_EX_LEFT | WS_EX_LTRREADING;
w_dwStyle |= WS_EX_RIGHT | WS_EX_RTLREADING;
}
else
{
w_dwStyle -= WS_EX_RIGHT | WS_EX_RTLREADING;
w_dwStyle |= WS_EX_LEFT | WS_EX_LTRREADING;
}
SetWindowLong(m_TextEdit.GetSafeHwnd(), GWL_EXSTYLE, w_dwStyle);
}
See my tip.

ADO: How to execute query synchronously with the ability to cancel?

Right now i have a function that runs a query, using ADO, and returns a recordset:
Recordset Execute(Connection connection, String commandText)
{
//[pseudo-code]
Recordset rs = new Recordset();
rs.CursorLocation = adUseClient;
rs.CursorType = adOpenForwardOnly;
rs.Open(commandText, connection,
adOpenForwardOnly, //CursorType; the default
adLockReadOnly, //LockType
adCmdText);
return rs;
}
And this is fine. It runs synchronously, and returns the recordset of a query.
Now i want a similar version, that shows a ProgressDialog, providing the user with the ability to cancel a long-running query:
Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText)
{
//[pseudo-code]
//Construct a progressDialog and show it
IProgressDialog pd = new ProgressDialog();
pd.SetTitle(caption); //e.g. "Annual Funding Report"
pd.SetCancelMsg("Please wait while the operation is cancelled");
pd.StartProgressDialog(parenthWnd, null, PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOMINIMIZE, null);
pd.SetLine(1, "Querying server", False, null);
try
{
//Query the server
Recordset rs = new Recordset();
rs.Open(commandText, connection,
adOpenForwardOnly, //CursorType
adLockReadOnly, //LockType
adCmdText | adAsyncExecute);
while (rs.State and (adStateConnecting+adStateExecuting+adStateFetching) <> 0)
{
if pd.HasUserCancelled()
throw new EUserCancelledOperationException();
Sleep(100);
};
finally
{
//Hide and destroy the progress dialog
pd.StopProgressDialog();
pd = null;
}
//Now we have our results for the client
return rs;
}
The way to check if the user has cancelled the operation is to periodically ask the progress dialog if the user as pressed the cancel button:
pd.HasUserCancelled(); //returns true if user has clicked Cancel
Now i'm faced with how to periodically check if the user has cancelled (and to know if the query has completed, or an error has happened), and to be a good programmer and do it without polling.
The only way to know that an error has happened is to have a handler on the Recordset's FetchCompleteEvent:
pError
An Error object. It describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set.
adStatus
An EventStatusEnum status value. When this event is called, this parameter is set to adStatusOK if the operation that caused the event was successfull, or to adStatusErrorsOccurred if the operation failed.
Before this event returns, set this parameter to adStatusUnwantedEvent to prevent subsequent notifications.
pRecordset
A Recordset object. The object for which the records were retrieved.
So this would imply that i'm going to have to have my function construct a helper object, so i can have a FetchComplete handler. But then i have to prevent my synchronous function from returning right away. And then we get into MsgWaitForSingleObject, which is notoriously difficult to use correctly.
So i'm asking for assistance, or canned code.
i should be more explicit: i'm looking for an implementation of function with this method signature:
Recordset ExecuteWithCancelOption(Connection connection, String commandText)
that shows a dialog with a cancel button on it.
The challenge is that the function must now create whatever is required to achieve that. If that involves a hidden form, that has a timer on it, etc - okay.
But i'm looking for a synchronous function that displays a Cancel button.
And the function is going to be a near (or exact) drop-in replacement for
Recordset Execute(Connection connection, String commandText)
Given practical considerations on Windows, i would need to supply the function with a parent window handle that it will parent its dialog to:
Recordset ExecuteWithCancelOption(HWND parentHwnd, Connection connection, String commandText)
And given that this is going to be a reusable function, i'll let the caller provide the text that will be displayed:
Recordset ExecuteWithCancelOption(HWND parenthWnd, String caption, Connection connection, String commandText)
And given that these are both class functions in my TADOHelper class, i can give them the same name, and have them be overloads of one another:
Recordset Execute(HWND parenthWnd, String caption, Connection connection, String commandText)
i would think in languages other than Delphi, anonymous delegates are helpful. But i'm still terrified of having to deal with MsgWaitForMultipleObjects.
Progress informations and gracefully cancelling a query may not be available in every database engine. They need database support, both on the server and the client side. For example Oracle allows cancelling a query, yet has no "on progress" information but reading the V$SESSION_LONGOPS view. Sure, you can kill the session, but it will rollback the whole of it, not just cancel a give query execution.
Usually if the database supports this kind of features, the query is run in a separate thread that will wait for the result. That way the main thread can still get user input or read and display progress information (unless returned in some kind of callback). If the user cancels the query then the appropriate call is issued to stop the operation, allowing the query thread to return, usually the thread will receive a status code that will tell what's happened.
Be aware of how ADO implements async operations: http://msdn.microsoft.com/en-us/library/ms681467(VS.85).aspx
There's also a FetchProgress() event that could help you if you don't want to go the thread way (and even then to cancel a the query, if possible)
In order to make GUI to respond to button clicks you should return control to the message loop of the window. While loop while (rs.State and (adStateConnecting+adStateExecuting+adStateFetching) <> 0) does not return control back to the message loop thus blocking GUI.
Below is an excerpt from a working Delphi code that uses asyncronous ADO queries. This code does not allow for non-modal fetching of data, but ensures that the main form is repainted during data fetch and also allows cancelling the query.
Asynchronous execution and fetching is achieved by setting:
FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking];
execution of query is cancelled by calling
DataSet.Recordset.Cancel;
in FetchProgress event.
Any TADODataSet shall be opened via the method:
OpenDataSetInBackground(DataSourceData.DataSet as TADODataSet);
Supporting code in the main form:
procedure TOperatorForm.OpenDataSetInBackground(DataSet: TADODataSet);
begin
if DataSet.Active then Exit;
FOpeningDataSet := DataSet;
if not FAsyncDataFetch then
begin
FOpeningDataSet.Open;
Exit;
end;
FFetchCancel := False;
FExecuteOptions := FOpeningDataSet.ExecuteOptions;
FFetchProgress := FOpeningDataSet.OnFetchProgress;
FFetchComplete := FOpeningDataSet.OnFetchComplete;
FRecordsetCreate := FOpeningDataSet.OnRecordsetCreate;
FAfterScroll := FOpeningDataSet.AfterScroll;
FOpeningDataSet.ExecuteOptions := [eoAsyncExecute, eoAsyncFetchNonBlocking];
FOpeningDataSet.OnFetchProgress := DataSetFetchProgress;
FOpeningDataSet.OnFetchComplete := DataSetFetchComplete;
FOpeningDataSet.OnRecordsetCreate := DataSetRecordsetCreate;
FOpeningDataSet.AfterScroll := DataSetAfterScroll;
FOpeningDataSet.CursorLocation := clUseClient;
FOpeningDataSet.DisableControls;
try
DataSetProgressForm.Left := Left + (Width - DataSetProgressForm.Width) div 2;
DataSetProgressForm.Top := Top + (Height - DataSetProgressForm.Height) div 2;
DataSetProgressForm.cxButton1.OnClick := DataSetProgressClick;
DataSetProgressForm.cxButton1.Visible := FShowProgressCancelButton;
FOpeningDataSet.Open;
DataSetProgressForm.ShowModal;
finally
FOpeningDataSet.EnableControls;
FOpeningDataSet.ExecuteOptions := FExecuteOptions;
FOpeningDataSet.OnFetchProgress := FFetchProgress;
FOpeningDataSet.OnFetchComplete := FFetchComplete;
FOpeningDataSet.OnRecordsetCreate := FRecordsetCreate;
FOpeningDataSet.AfterScroll := FAfterScroll;
end;
end;
procedure TOperatorForm.DataSetProgressClick(Sender: TObject);
begin
FFetchCancel := True;
end;
procedure TOperatorForm.DataSetFetchProgress(DataSet: TCustomADODataSet; Progress, MaxProgress: Integer; var EventStatus: TEventStatus);
begin
if FFetchCancel then
DataSet.Recordset.Cancel;
end;
procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus);
begin
PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0);
MessageBeep(MB_ICONEXCLAMATION);
end;
procedure TOperatorForm.DataSetFetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus);
begin
PostMessage(DataSetProgressForm.Handle, WM_CLOSE, 0, 0);
MessageBeep(MB_ICONEXCLAMATION);
end;
procedure TOperatorForm.DataSetRecordsetCreate(DataSet: TCustomADODataSet; const Recordset: _Recordset);
begin
if Assigned(FRecordsetCreate) then FRecordsetCreate(DataSet, Recordset);
end;
procedure TOperatorForm.DataSetAfterScroll(DataSet: TDataSet);
begin
// From TBetterADODataSet 4.04
// Ole Willy Tuv's fix 03-10-00 for missing first record
with TADODataSet(DataSet) do
begin
if (eoAsyncFetchNonBlocking in ExecuteOptions) and
(Bof or Eof) and
(CursorLocation = clUseClient) and
(stFetching in RecordSetState) then
begin
if Recordset.RecordCount > 0 then
if Bof then
Recordset.MoveFirst
else if Eof then
Recordset.MoveLast;
CursorPosChanged;
Resync([]);
end;
end;
if Assigned(FAfterScroll) then
FAfterScroll(DataSet);
end;
Progress form:
unit uDataSetProgressForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, ExtCtrls, StdCtrls;
type
TDataSetProgressForm = class(TForm)
AnimateProgress: TAnimate;
Label1: TLabel;
Bevel1: TBevel;
Bevel2: TBevel;
Button1: TButton;
Shape1: TShape;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormHide(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
DataSetProgressForm: TDataSetProgressForm;
implementation
{$R *.dfm}
{$R servertimeout.res} // contains IDR_SERVAVI animation resource
procedure TDataSetProgressForm.FormCreate(Sender: TObject);
begin
AnimateProgress.ResName := 'IDR_SERVAVI';
end;
procedure TDataSetProgressForm.FormShow(Sender: TObject);
begin
AnimateProgress.Active := True;
end;
procedure TDataSetProgressForm.FormHide(Sender: TObject);
begin
AnimateProgress.Active := False;
end;
end.
and dfm
object DataSetProgressForm: TDataSetProgressForm
Left = 590
Top = 497
BorderStyle = bsNone
ClientHeight = 104
ClientWidth = 205
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
FormStyle = fsStayOnTop
OldCreateOrder = False
Position = poDefaultSizeOnly
OnCreate = FormCreate
OnHide = FormHide
OnShow = FormShow
DesignSize = (
205
104)
PixelsPerInch = 96
TextHeight = 13
object Bevel1: TBevel
Left = 0
Top = 0
Width = 205
Height = 104
Align = alClient
Style = bsRaised
end
object Bevel2: TBevel
Left = 12
Top = 12
Width = 181
Height = 80
Anchors = [akLeft, akTop, akRight, akBottom]
end
object Shape1: TShape
Left = 1
Top = 1
Width = 203
Height = 102
Anchors = [akLeft, akTop, akRight, akBottom]
Brush.Style = bsClear
Pen.Color = clWindowFrame
end
object AnimateProgress: TAnimate
Left = 25
Top = 23
Width = 32
Height = 32
end
object Label1: TLabel
Left = 70
Top = 31
Width = 106
Height = 17
Hint = 'Selecting data...'
Caption = 'Selecting data...'
TabOrder = 1
end
object Button1: TButton
Left = 63
Top = 64
Width = 80
Height = 23
Caption = 'Cancel'
Default = True
TabOrder = 2
end
end
If it is Delphi you can drop a TTimer component in and use that to check if HasUserCancelled value is True. I don't have Delphi in front of me so I'd have to post an example later.
Edit:
Here's an example of a TTimer OnTimer event that checks the current time and the lastactivity time to decide what to do with the forms if the program has been left "Up":
procedure TForm_Main.Timer1Timer(Sender: TObject);
begin
// return to opening screen if no activity for a while:
if Now - LastActivity > TimeOut
then
begin
Form_Select.SBtn_Defendant.Down:= False;
Form_Select.SBtn_Officer.Down:= False;
Form_Select.SBtn_Attorney.Down:= False;
Form_Main.ModalResult:= mrCancel;
Exit;
end;
Form_Main.Caption:= FormatDateTime('dddd mmmm d, yyyy h:nn:ss AM/PM', Now);
end;