Edge sense controller, Smart style
Yesterday I posted a short low-down on how to use TW3Dataset in your Smart Mobile Studio applications; I also included the source code for a CSS powered, heavy duty DB grid I’m working on.
Today I’m going to extract on one of the concepts I use in that DB grid, isolate it as a separate class and explain what you can do with it.
Edge sense
Being able to sense when the mouse-pointer is near the edge of a control (any edge) is very useful, especially when dealing with presentation of columns and rows you want to make re-sizable. It can however be a bit tricky to master.
Below you will find the working source-code for a completely separate controller class which adds edge-sense functionality. I have written it as a controller and not a part of TW3Customcontrol (or inheriting from any other visual component) to make sure it can be used more generally. As it stands now it will happily attach itself to any TW3MovableControl based visual component, create its own event handlers and update the mouse-pointer automatically.
Why controller?
Well, imagine you are creating a DB grid header component. Naturally you want your users to be able to resize the columns as they see fit. Well, this can be achieved in two ways. The first is that you implement edge-sensing directly into the column components themselves. But that means a lot of extra work. You would have to derive your column from a baseclass, hook mouse events and well, basically implement exactly what you get for free below.
The second option, which I have used here, is to have a separate “sense” object which keeps track of everything without interfering with any events or behavior. This means a bit more code to achieve but once you have isolated it as a class, you can apply it to nearly all visual controls.
A few words about abstraction
When writing your own custom components, always derive your own class-names even though you dont alter behavior. This should always be respected. First of all because of styling (css styles wont collide, and you can now safely style every aspect of your control) but also because of clarity.
So if your grid-header is made up of labels (which makes sense, considering a header with columns display, well, text) dont just use TW3Label. Derive your own class from TW3label and use that, like this:
type TDBHeaderItem = class(TW3Label) end; TW3HeaderControl = class(TW3CustomControl) protected procedure InitializeObject;Override; end; procedure TW3HeaderControl.InitializeObject; begin inherited; var first := TDBHeaderItem.Create(self); var second := TDBHeaderItem.Create(self); .. handle.ReadyExecute( procedure () begin Resize; end); end;
In the above code, if we had just created a series of TW3Label’s, there would be no way of styling the columns without ruining the style for *ALL* TW3Label elements in your entire application. By simply deriving your own class and using that, you can now safely a style in the stylesheet which affects just your header-item(s).
The edge sense controller
Here is the code. To use it, simply give it the visual component you want to have sense-control in the constructor, and that’s basically it. You must expand the class yourself to add size/move functionality but that should be easy (and self explanatory):
type TEdgeRegions = (scLeft,scTop,scRight,scBottom,scNone); TEdgeSenseEdges = set of TEdgeRegions; TEdgeSenseController = class(TW3OwnedObject) const CN_LEFT = 0; CN_TOP = 1; CN_RIGHT = 2; CN_BOTTOM = 3; private FEdgeSize:Integer; FEdges: TEdgeSenseEdges; FRects: Array[0..3] of TRect; FEdgeId: Integer; FBound: Boolean; procedure CBMouseMove(eventObj : JMouseEvent); procedure CBMouseEnter(eventObj : JMouseEvent); procedure CBMouseExit(eventObj : JMouseEvent); procedure SetEdgeSize(const Value:Integer); Procedure SetSenseEdges(const Value:TEdgeSenseEdges); function GetActiveEdge:TEdgeRegions; procedure CheckCorners(const x,y:Integer); function GetActiveEdgeOffset(x,y:Integer):TPoint; protected procedure MouseMove(shiftState:TShiftState;x,y:Integer); procedure MouseEnter(shiftState:TShiftState;x,y:Integer); procedure MouseExit(shiftState:TShiftState;x,y:Integer); public procedure BindToControl; procedure UnBindFromControl; procedure UpdateCursor; procedure Update; Constructor Create(AOwner:TW3MovableControl);reintroduce; Destructor Destroy;Override; property EdgeSize:Integer read FEdgeSize write SetEdgeSize; Property ActiveEdge:TEdgeRegions read getActiveEdge; Property SenseEdges:TEdgeSenseEdges read FEdges write SetSenseEdges; end; Constructor TEdgeSenseController.Create(AOwner:TW3MovableControl); begin inherited Create(AOwner); FEdges:=[scLeft,scTop,scRight,scBottom]; FEdgeSize:=10; BindToControl; end; Destructor TEdgeSenseController.Destroy; begin UnBindFromControl; inherited; end; procedure TEdgeSenseController.Update; Begin if FBound then begin var LOwner := TW3MovableControl(Owner); FRects[0]:=TRect.CreateSized(0,EdgeSize,EdgeSize,LOwner.ClientHeight-EdgeSize); FRects[1]:=TRect.CreateSized(0,0,LOwner.ClientWidth,EdgeSize); FRects[2]:=TRect.CreateSized(LOwner.ClientWidth-EdgeSize,EdgeSize,LOwner.Clientwidth,LOwner.ClientHeight-EdgeSize); FRects[3]:=TRect.CreateSized(0,LOwner.ClientHeight-EdgeSize,LOwner.ClientWidth,EdgeSize); end else begin FRects[0]:=TRect.NullRect; FRects[1]:=TRect.NullRect; FRects[2]:=TRect.NullRect; FRects[3]:=TRect.NullRect; end; end; procedure TEdgeSenseController.UpdateCursor; var mCursor: string; LOwner: TW3MovableControl; procedure doDefault; begin if mCursor<>'default' then w3_setStyle(LOwner.Handle,'cursor','default'); end; begin LOwner := TW3MovableControl(Owner); mCursor:=w3_getStyleAsStr(LOwner.Handle,'cursor').LowerCase; case FEdgeId of CN_LEFT: Begin if (scLeft in FEdges) then begin if mCursor<>'col-resize' then w3_setStyle(LOwner.Handle,'cursor','col-resize'); end else doDefault; end; CN_RIGHT: Begin if (scRight in FEdges) then begin if mCursor<>'col-resize' then w3_setStyle(LOwner.Handle,'cursor','col-resize'); end else doDefault; end; CN_TOP: begin if (scTop in FEdges) then begin if mCursor<>'row-resize' then w3_setStyle(LOwner.Handle,'cursor','row-resize'); end else doDefault; end; CN_BOTTOM: begin if (scBottom in FEdges) then begin if mCursor<>'row-resize' then w3_setStyle(LOwner.Handle,'cursor','row-resize'); end else doDefault; end; else doDefault; end; end; procedure TEdgeSenseController.CheckCorners(const x,y:Integer); var mItem:TRect; mIndex: Integer; begin FEdgeId:=-1; for mItem in FRects do begin if mItem.ContainsPos(x,y) then begin FEdgeId:=mIndex; break; end; inc(mIndex); end; end; function TEdgeSenseController.GetActiveEdgeOffset(x,y:Integer):TPoint; begin if FEdgeId>=0 then result:=TPoint.Create ( x - FRects[FEdgeId].Left, y - FRects[FEdgeId].Top ); end; function TEdgeSenseController.getActiveEdge:TEdgeRegions; begin if (FEdgeId>=0) and (FEdgeId<=3) then result:=TEdgeRegions(FEdgeid) else result:=scNone; end; Procedure TEdgeSenseController.SetSenseEdges(const Value:TEdgeSenseEdges); begin if (scLeft in Value) then Include(FEdges,scLeft) else Exclude(FEdges,scLeft); if (scTop in Value) then include(FEdges,scTop) else Exclude(FEdges,scTop); if (scRight in Value) then Include(FEdges,scRight) else Exclude(FEdges,scRight); if (scBottom in Value) then include(FEdges,scBottom) else Exclude(FEdges,scBottom); end; procedure TEdgeSenseController.SetEdgeSize(const Value:Integer); begin FEdgeSize := TInteger.EnsureRange(Value,4,32); end; procedure TEdgeSenseController.BindToControl; begin if not FBound then begin var LOwner := TW3MovableControl(Owner); LOwner.handle.addEventListener('mousemove',@CBMouseMove,true); LOwner.handle.addEventListener('mouseover',@CBMouseMove,true); LOwner.handle.addEventListener('mouseout',@CBMouseMove,true); FBound := True; end; end; procedure TEdgeSenseController.UnBindFromControl; begin if FBound then begin var LOwner := TW3MovableControl(Owner); LOwner.handle.removeEventListener('mousemove',@CBMouseMove,true); LOwner.handle.removeEventListener('mouseover',@CBMouseMove,true); LOwner.handle.removeEventListener('mouseout',@CBMouseMove,true); FBound := false; end; end; procedure TEdgeSenseController.MouseMove(shiftState:TShiftState;x,y:Integer); begin if FBound then begin Update; CheckCorners(x,y); UpdateCursor; end; end; procedure TEdgeSenseController.MouseEnter(shiftState:TShiftState;x,y:Integer); begin if FBound then begin Update; CheckCorners(x,y); UpdateCursor; end; end; procedure TEdgeSenseController.MouseExit(shiftState:TShiftState;x,y:Integer); begin if FBound then begin Update; CheckCorners(x,y); UpdateCursor; end; end; procedure TEdgeSenseController.CBMouseMove(eventObj : JMouseEvent); begin var LOwner := TW3MovableControl(Owner); var sr := LOwner.ScreenRect; var shiftState := TShiftState.Current; shiftState.MouseEvent := eventObj; MouseMove(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top); end; procedure TEdgeSenseController.CBMouseEnter(eventObj : JMouseEvent); begin var LOwner := TW3MovableControl(Owner); var sr := LOwner.ScreenRect; var shiftState := TShiftState.Current; shiftState.MouseEvent := eventObj; MouseEnter(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top); end; procedure TEdgeSenseController.CBMouseExit(eventObj : JMouseEvent); begin var LOwner := TW3MovableControl(Owner); var sr := LOwner.ScreenRect; var shiftState := TShiftState.Current; shiftState.MouseEvent := eventObj; MouseExit(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top); end;
You must be logged in to post a comment.