1{%MainUnit wincewscomctrls.pp} 2{ 3 ***************************************************************************** 4 This file is part of the Lazarus Component Library (LCL) 5 6 See the file COPYING.modifiedLGPL.txt, included in this distribution, 7 for details about the license. 8 ***************************************************************************** 9} 10 11type 12 TCustomPageAccess = class(TCustomPage) 13 end; 14 15function IsNotebookGroupFocused(const ATabControl: TCustomTabControl): boolean; 16var 17 lNotebookHandle, lWindow: HWND; 18begin 19 result := false; 20 if not ATabControl.HandleAllocated then exit; 21 lNotebookHandle := ATabControl.Handle; 22 lWindow := Windows.GetFocus; 23 while (lWindow <> 0) and (lWindow <> lNotebookHandle) do 24 lWindow := Windows.GetParent(lWindow); 25 if lWindow = 0 then exit; 26 result := true; 27end; 28 29{ sets focus to a control on the newly focused tab page } 30procedure NotebookFocusNewControl(const ATabControl: TCustomTabControl; NewIndex: integer); 31var 32 Page: TCustomPage; 33 AWinControl: TWinControl; 34 ParentForm: TCustomForm; 35begin 36 { see if currently focused control is within notebook } 37 if not IsNotebookGroupFocused(ATabControl) then exit; 38 39 { focus was/is within notebook, pick a new control to focus } 40 Page := ATabControl.CustomPage(NewIndex); 41 ParentForm := GetParentForm(ATabControl); 42 if ParentForm <> nil then 43 begin 44 if ATabControl.ContainsControl(ParentForm.ActiveControl) and (ParentForm.ActiveControl <> ATabControl) then 45 begin 46 AWinControl := nil; 47 if Page.CanFocus then 48 AWinControl := TCustomPageAccess(Page).FindNextControl(nil, True, True, False); 49 // if nothing to focus then focus notebook then we can traverse pages by keys 50 if AWinControl = nil then 51 AWinControl := ATabControl; 52 AWinControl.SetFocus; 53 end; 54 end; 55end; 56 57function NotebookPageRealToLCLIndex(const ATabControl: TCustomTabControl; AIndex: integer): integer; 58var 59 I: Integer; 60begin 61 Result := AIndex; 62 if csDesigning in ATabControl.ComponentState then exit; 63 I := 0; 64 while (I < ATabControl.PageCount) and (I <= Result) do 65 begin 66 if not ATabControl.Page[I].TabVisible then Inc(Result); 67 Inc(I); 68 end; 69end; 70 71{ TWinCEWSCustomPage } 72 73function PageWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam; 74 LParam: Windows.LParam): LResult; {$ifdef win32}stdcall{$else}cdecl{$endif}; 75begin 76 Result := WindowProc(Window, Msg, WParam, LParam); 77end; 78 79class function TWinCEWSCustomPage.CreateHandle(const AWinControl: TWinControl; 80 const AParams: TCreateParams): HWND; 81var 82 Params, PanelParams: TCreateWindowExParams; 83 init : TINITCOMMONCONTROLSEX; 84 lPanel: TPanel; 85 WindowInfo: PWindowInfo; 86begin 87// DebugLn('Creating CustomPage A'); 88 init.dwSize := Sizeof(TINITCOMMONCONTROLSEX); 89 init.dwICC := ICC_TAB_CLASSES; 90 InitCommonControlsEx(@init); 91 92 // First create the TabSheet 93 94 // general initialization of Params 95 PrepareCreateWindow(AWinControl, AParams, Params); 96 // customization of Params 97 with Params do 98 begin 99 pClassName := @ClsName; 100 SubClassWndProc := @PageWindowProc; 101 Flags := Flags and not WS_VISIBLE; 102 end; 103 // create window 104 FinishCreateWindow(AWinControl, Params, false); 105 106 // return window handle 107 Result := Params.Window; 108 Params.WindowInfo^.ParentPanel := Params.Window; 109 110{ // The standard control created to show a tabsheet is unable to 111 // show non-windowed controls inside it, no matter what is done. 112 // The found solution was to add a panel to it and place all child 113 // controls inside the panel instead. 114 lPanel := TPanel.Create(nil); 115 lPanel.BevelOuter := bvNone; 116 lPanel.Left := 0; 117 lPanel.Top := 0; 118 lPanel.Width := Params.Width; 119 lPanel.Height := Params.Height; 120 lPanel.Align := alClient; 121 lPanel.ParentWindow := Params.Window; 122 lPanel.HandleNeeded; 123 WindowInfo := GetWindowInfo(lPanel.Handle); 124 125 // return window handle 126 Result := lPanel.Handle; 127 WindowInfo^.ParentPanel := Params.Window;} 128 129// DebugLn(Format('Creating CustomPage B Panel: %s Page: %s', 130// [IntToHex(PanelParams.Window, 8), IntToHex(Params.Window, 8)])); 131end; 132 133class procedure TWinCEWSCustomPage.DestroyHandle(const AWinControl: TWinControl); 134var 135 PageIndex, RealIndex: integer; 136 PageControlHandle: HWND; 137begin 138 // remove tab from pagecontrol only if not pfRemoving is set 139 // if pfRemoving is set then Tab has been deleted by RemovePage 140 if (AWinControl.Parent <> nil) and (AWinControl.Parent.HandleAllocated) and 141 not (pfRemoving in TCustomPageAccess(AWinControl).Flags) then 142 begin 143 PageControlHandle := AWinControl.Parent.Handle; 144 PageIndex := TCustomPage(AWinControl).PageIndex; 145 RealIndex := TCustomTabControl(AWinControl.Parent).PageToTabIndex(PageIndex); 146 if RealIndex <> -1 then 147 Windows.SendMessage(PageControlHandle, TCM_DELETEITEM, 148 Windows.WPARAM(RealIndex), 0); 149 end; 150 TWSWinControlClass(ClassParent).DestroyHandle(AWinControl); 151end; 152 153class procedure TWinCEWSCustomPage.SetText(const AWinControl: TWinControl; const AText: string); 154var 155 TCI: TC_ITEM; 156 PageIndex, RealIndex: integer; 157 NotebookHandle: HWND; 158begin 159 PageIndex := TCustomPage(AWinControl).PageIndex; 160 RealIndex := TCustomTabControl(AWinControl.Parent).PageToTabIndex(PageIndex); 161 NotebookHandle := AWinControl.Parent.Handle; 162 // We can't set label of a page not yet added, 163 // Check for valid page index 164 if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(NotebookHandle, TCM_GETITEMCOUNT, 0, 0)) then 165 begin 166 // retrieve page handle from tab as extra check (in case page isn't added yet). 167 TCI.mask := TCIF_PARAM; 168 Windows.SendMessageW(NotebookHandle, TCM_GETITEMW, RealIndex, LPARAM(@TCI)); 169 if PtrUInt(TCI.lParam)=PtrUInt(AWinControl) then 170 begin 171 //DebugLn(Format('Trace:TWinCEWSCustomPage.SetText --> %S', [AText])); 172 TCI.mask := TCIF_TEXT; 173 {$ifdef Win32} 174 TCI.pszText := PChar(PWideChar(UTF8Decode(AText))); 175 {$else} 176 TCI.pszText := PWideChar(UTF8Decode(AText)); 177 {$endif} 178 Windows.SendMessageW(NotebookHandle, TCM_SETITEMW, RealIndex, LPARAM(@TCI)); 179 FreeMem(TCI.pszText); 180 end; 181 end; 182end; 183 184class procedure TWinCEWSCustomPage.UpdateProperties(const ACustomPage: TCustomPage); 185var 186 TCI: TC_ITEM; 187 PageIndex, RealIndex: integer; 188 NotebookHandle: HWND; 189begin 190 PageIndex := ACustomPage.PageIndex; 191 RealIndex := TCustomTabControl(ACustomPage.Parent).PageToTabIndex(PageIndex); 192 NotebookHandle := ACustomPage.Parent.Handle; 193 // Check for valid page index 194 if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(NotebookHandle, TCM_GETITEMCOUNT,0,0)) then 195 begin 196 // retrieve page handle from tab as extra check (in case page isn't added yet). 197 TCI.mask := TCIF_PARAM; 198 Windows.SendMessage(NotebookHandle, TCM_GETITEM, RealIndex, LPARAM(@TCI)); 199 if PtrUInt(TCI.lParam) = PtrUInt(ACustomPage) then 200 begin 201 TCI.mask := TCIF_IMAGE; 202 TCI.iImage := TCustomTabControl(ACustomPage.Parent).GetImageIndex(PageIndex); 203 204 Windows.SendMessage(NotebookHandle, TCM_SETITEM, RealIndex, LPARAM(@TCI)); 205 end; 206 end; 207end; 208 209{ TWinCEWSCustomNotebook } 210 211class function TWinCEWSCustomNotebook.CreateHandle(const AWinControl: TWinControl; 212 const AParams: TCreateParams): HWND; 213// The prefered style for the non-supported ones is bottom, as per MS guidelines, 214// so that the user won't cover the screen with the hand while changing tabs 215// Left and Right styles aren't supported because vertical text isn't supported 216// See: http://wiki.lazarus.freepascal.org/Windows_CE_Development_Notes#Tab_Controls_.28TPageControl.29 217const 218 TabPositionFlags: array[TTabPosition] of DWord = ( 219 { tpTop } 0, 220 { tpBottom } TCS_BOTTOM, 221 { tpLeft } TCS_BOTTOM, //TCS_VERTICAL or TCS_MULTILINE, 222 { tpRight } TCS_BOTTOM //TCS_VERTICAL or TCS_RIGHT or TCS_MULTILINE 223 ); 224var 225 Params: TCreateWindowExParams; 226 init : TINITCOMMONCONTROLSEX; 227begin 228 init.dwSize := Sizeof(TINITCOMMONCONTROLSEX); 229 init.dwICC := ICC_TAB_CLASSES; 230 InitCommonControlsEx(@init); 231 // general initialization of Params 232 PrepareCreateWindow(AWinControl, AParams, Params); 233 // customization of Params 234 with Params do 235 begin 236 Flags := Flags or TabPositionFlags[TCustomTabControl(AWinControl).TabPosition]; 237 if nboMultiLine in TCustomTabControl(AWinControl).Options then 238 Flags := Flags or TCS_MULTILINE; 239 pClassName := WC_TABCONTROL; 240 end; 241 // create window 242 FinishCreateWindow(AWinControl, Params, false); 243 Result := Params.Window; 244 245 if TCustomTabControl(AWinControl).Images <> nil then 246 SendMessage(Result, TCM_SETIMAGELIST, 0, TCustomImageListResolution(TCustomTabControl(AWinControl).Images).Reference._Handle); 247 248 // although we may be child of tabpage, cut the paint chain 249 // to improve speed and possible paint anomalities 250 Params.WindowInfo^.needParentPaint := False; 251 252 // The Windows CE tab controls are backwards compatible with older versions 253 // so we need to specify if we desire the more modern flat style manually 254 //SendMessage(Params.Window, CCM_SETVERSION, COMCTL32_VERSION, 0); 255end; 256 257class procedure TWinCEWSCustomNotebook.AddPage(const ATabControl: TCustomTabControl; 258 const AChild: TCustomPage; const AIndex: integer); 259var 260 TCI: TC_ITEM; 261 WideStr: widestring; 262begin 263 with ATabControl do 264 begin 265 AChild.HandleNeeded; 266 if ShowTabs then 267 begin 268 TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE; 269 // store object as extra, so we can verify we got the right page later 270 TCI.lParam := PtrUInt(AChild); 271 TCI.iImage := ATabControl.GetImageIndex(NotebookPageRealToLCLIndex(ATabControl, AIndex)); 272 WideStr := UTF8Decode(AChild.Caption); 273 {$ifdef Win32} 274 TCI.pszText := PChar(PWideChar(WideStr)); 275 {$else} 276 TCI.pszText := PWideChar(WideStr); 277 {$endif} 278 Windows.SendMessageW(Handle, TCM_INSERTITEMW, AIndex, LPARAM(@TCI)); 279 end; 280 // clientrect possible changed, adding first tab, or deleting last 281 // windows should send a WM_SIZE message because of this, but it doesn't 282 // send it ourselves 283 LCLControlSizeNeedsUpdate(ATabControl, True); 284 end; 285end; 286 287class procedure TWinCEWSCustomNotebook.MovePage(const ATabControl: TCustomTabControl; 288 const AChild: TCustomPage; const NewIndex: integer); 289begin 290 RemovePage(ATabControl, AChild.PageIndex); 291 AddPage(ATabControl, AChild, NewIndex); 292end; 293 294class procedure TWinCEWSCustomNotebook.RemovePage(const ATabControl: TCustomTabControl; 295 const AIndex: integer); 296begin 297 Windows.SendMessage(ATabControl.Handle, TCM_DELETEITEM, Windows.WPARAM(AIndex), 0); 298end; 299 300{ ----------------------------------------------------------------------------- 301 Method: AddAllNBPages 302 Params: Notebook - A notebook control 303 Returns: Nothing 304 305 Adds all pages to notebook (showtabs becomes true) 306 ------------------------------------------------------------------------------} 307class procedure TWinCEWSCustomNotebook.AddAllNBPages(const ATabControl: TCustomTabControl); 308var 309 TCI: TC_ITEM; 310 I, Res, RealIndex: Integer; 311 APage: TCustomPage; 312 WinHandle: HWND; 313begin 314 WinHandle := ATabControl.Handle; 315 RealIndex := 0; 316 for I := 0 to ATabControl.PageCount - 1 do 317 begin 318 APage := ATabControl.Page[I]; 319 if not APage.TabVisible and not (csDesigning in APage.ComponentState) then 320 continue; 321 // check if already shown 322 TCI.Mask := TCIF_PARAM; 323 Res := Windows.SendMessage(ATabControl.Handle, TCM_GETITEM, RealIndex, LPARAM(@TCI)); 324 if (Res = 0) or (PtrUInt(TCI.lParam) <> PtrUInt(APage)) then 325 begin 326 TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE; 327 TCI.lParam := PtrUInt(APage); 328 TCI.iImage := ATabControl.GetImageIndex(I); 329 {$ifdef Win32} 330 TCI.pszText := PChar(PWideChar(UTF8Decode(APage.Caption))); 331 {$else} 332 TCI.pszText := PWideChar(UTF8Decode(APage.Caption)); 333 {$endif} 334 Windows.SendMessageW(WinHandle, TCM_INSERTITEMW, RealIndex, LPARAM(@TCI)); 335 end; 336 Inc(RealIndex); 337 end; 338 AdjustSizeNotebookPages(ATabControl); 339end; 340 341class procedure TWinCEWSCustomNotebook.AdjustSizeNotebookPages(const ATabControl: TCustomTabControl); 342var 343 I: Integer; 344 R: TRect; 345 WinHandle: HWND; 346 lPage: TCustomPage; 347begin 348 WinHandle := ATabControl.Handle; 349 350 // Adjust page size to fit in tabcontrol, need bounds of notebook in client of parent 351 LCLIntf.GetClientRect(WinHandle, R); 352 353 for I := 0 to ATabControl.PageCount - 1 do 354 begin 355 lPage := ATabControl.Page[I]; 356 // we don't need to resize non-existing pages yet, they will be sized when created 357 if lPage.HandleAllocated then 358 // The Windows CE notebook as some alignment problems which we need to workaround 359 // by adding an extra change to the position it gives us for the sheet position 360 SetBounds(lPage, R.Left - 3, R.Top, R.Right - R.Left + 3, R.Bottom - R.Top); 361 end; 362end; 363 364{------------------------------------------------------------------------------ 365 Method: RemoveAllNBPages 366 Params: Notebook - The notebook control 367 Returns: Nothing 368 369 Removes all pages from a notebook control (showtabs becomes false) 370 ------------------------------------------------------------------------------} 371class procedure TWinCEWSCustomNotebook.RemoveAllNBPages(const ATabControl: TCustomTabControl); 372var 373 I: Integer; 374 WinHandle: HWND; 375begin 376 WinHandle := ATabControl.Handle; 377 for I := ATabControl.PageCount - 1 downto 0 do 378 Windows.SendMessage(WinHandle, TCM_DELETEITEM, Windows.WPARAM(I), 0); 379 AdjustSizeNotebookPages(ATabControl); 380end; 381 382procedure SendSelChangeMessage(const ATabControl: TCustomTabControl; const AHandle: HWND; 383 const APageIndex: integer); 384var 385 Mess: TLMNotify; 386 NMHdr: tagNMHDR; 387begin 388 FillChar(Mess,SizeOf(Mess),0); 389 Mess.Msg := LM_NOTIFY; 390 FillChar(NMHdr,SizeOf(NMHdr),0); 391 NMHdr.code := TCN_SELCHANGE; 392 NMHdr.hwndfrom := AHandle; 393 NMHdr.idfrom := APageIndex; //use this to set pageindex to the correct page. 394 Mess.NMHdr := @NMHdr; 395 DeliverMessage(ATabControl, Mess); 396end; 397 398class function TWinCEWSCustomNotebook.GetTabIndexAtPos(const ATabControl: TCustomTabControl; 399 const AClientPos: TPoint): integer; 400var 401 hittestInfo: TC_HITTESTINFO; 402begin 403 hittestInfo.pt.X := AClientPos.X; 404 hittestInfo.pt.Y := AClientPos.Y; 405 Result := Windows.SendMessage(ATabControl.Handle, TCM_HITTEST, 0, LPARAM(@hittestInfo)); 406end; 407 408class function TWinCEWSCustomNotebook.GetTabRect( 409 const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; 410var 411 Orect: TRect; 412begin 413 GetLCLClientBoundsOffset(ATabControl, ORect); 414 if Windows.SendMessage(ATabControl.Handle, TCM_GETITEMRECT, WPARAM(AIndex), LPARAM(@Result)) <> 0 415 then begin 416 Result.Top := Result.Top - Orect.Top; 417 Result.Bottom := Result.Bottom - Orect.Top; 418 Result.Left := Result.Left - Orect.Left; 419 Result.Right := Result.Right - Orect.Left; 420 end 421 else 422 Result := inherited GetTabRect(ATabControl, AIndex); 423end; 424 425class function TWinCEWSCustomNotebook.GetCapabilities: TCTabControlCapabilities; 426begin 427 Result:=[]; 428end; 429 430class function TWinCEWSCustomNotebook.GetDesignInteractive( 431 const AWinControl: TWinControl; AClientPos: TPoint): Boolean; 432begin 433 Result:=inherited GetDesignInteractive(AWinControl, AClientPos); 434end; 435 436class procedure TWinCEWSCustomNotebook.SetImageList( 437 const ATabControl: TCustomTabControl; const AImageList: TCustomImageList); 438begin 439 if not WSCheckHandleAllocated(ATabControl, 'SetImageList') then 440 Exit; 441 442 if AImageList <> nil then 443 SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, AImageList.Reference[ATabControl.ImagesWidth]._Handle) 444 else 445 SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, 0); 446end; 447 448class procedure TWinCEWSCustomNotebook.SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); 449var 450 Handle: HWND; 451 PageHandle: HWND; 452 OldIndex, OldRealIndex, NewRealIndex: Integer; 453 WindowInfo: PWindowInfo; 454begin 455 Handle := ATabControl.Handle; 456 OldRealIndex := SendMessage(Handle, TCM_GETCURSEL, 0, 0); 457 OldIndex := NotebookPageRealToLCLIndex(ATabControl, OldRealIndex); 458 NewRealIndex := ATabControl.PageToTabIndex(AIndex); 459 SendMessage(Handle, TCM_SETCURSEL, Windows.WParam(NewRealIndex), 0); 460 if not (csDestroying in ATabControl.ComponentState) then 461 begin 462 // create handle if not already done, need to show! 463 if (AIndex >= 0) and (AIndex < ATabControl.PageCount) then 464 begin 465// PageHandle := ATabControl.CustomPage(AIndex).Handle; 466 WindowInfo := GetWindowInfo(ATabControl.CustomPage(AIndex).Handle); 467 PageHandle := WindowInfo^.ParentPanel; 468 469 SetWindowPos(PageHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW); 470 SendSelChangeMessage(ATabControl, Handle, AIndex); 471 NotebookFocusNewControl(ATabControl, AIndex); 472 end; 473 if (OldIndex >= 0) and (OldIndex <> AIndex) and 474 (OldIndex < ATabControl.PageCount) and 475 (ATabControl.CustomPage(OldIndex).HandleAllocated) then 476 ShowWindow(ATabControl.CustomPage(OldIndex).Handle, SW_HIDE); 477 end; 478end; 479 480{ Nothing can be done here because WinCE only supports tabs on the bottom } 481class procedure TWinCEWSCustomNotebook.SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); 482begin 483 484end; 485 486class procedure TWinCEWSCustomNotebook.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); 487begin 488 if AShowTabs then 489 AddAllNBPages(ATabControl) 490 else 491 RemoveAllNBPages(ATabControl); 492end; 493 494class procedure TWinCEWSCustomNotebook.UpdateProperties( 495 const ATabControl: TCustomTabControl); 496begin 497 inherited UpdateProperties(ATabControl); 498end; 499 500