По мотивам:
Про "примеси", шаблоны и Generic'и №3
И ещё раз про "примеси". Теперь - "серьёзно"
Коротко. Контроль за созданием/освобождением объектов. Только код. №4
Теперь вот какая штука.
Есть:
TmsDiagramms - это список TmsDiagramm.
TmsDiagramm - это список TmsShape.
Явно - "рекурсивное решение".
Как это обобщить?
Ну примерно так:
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/?at=MS-6_AddTestToMindStream
TmsItemsHolder.mixin.pas:
msDiagramm.pas:
msDiagramms.pas:
Ну и сериализация:
TmsMarshal.mixin.pas:
Ну и тесты:
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/?at=MS-6_AddTestToMindStream
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/TestmsSerializeController.pas?at=MS-6_AddTestToMindStream
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/msParametrizedShapeTestSuite.pas?at=MS-6_AddTestToMindStream
Вряд ли кому-то понравится. Я знаю.
Про "примеси", шаблоны и Generic'и №3
И ещё раз про "примеси". Теперь - "серьёзно"
Коротко. Контроль за созданием/освобождением объектов. Только код. №4
Теперь вот какая штука.
Есть:
TmsDiagramms - это список TmsDiagramm.
TmsDiagramm - это список TmsShape.
Явно - "рекурсивное решение".
Как это обобщить?
Ну примерно так:
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/?at=MS-6_AddTestToMindStream
TmsItemsHolder.mixin.pas:
{$IfNDef TmsItemsHolder_uses_intf} // Объект содержащий список других объектов // interface {$Define TmsItemsHolder_uses_intf} // uses Generics.Collections, Data.DBXJSONReflect, System.Rtti {$Else TmsItemsHolder_uses_intf} {$IfNDef TmsItemsHolder_intf} // http://programmingmindstream.blogspot.ru/2014/12/generic-2.html {$Define TmsItemsHolder_intf} TmsRttiFieldLambda = reference to procedure (aField: TRttiField); TmsItemsList = TList<TmsItem>; TmsItemsHolder = class(TmsItemsHolderParent) private [JSONMarshalled(True)] f_Items : TmsItemsList; function pm_GetItems: TmsItemsList; procedure pm_SetItems(aValue: TmsItemsList); class procedure RegisterItemsLike(aLambda: TmsRttiFieldLambda); public constructor Create; destructor Destroy; override; property Items: TmsItemsList read pm_GetItems write pm_SetItems; procedure Assign(anOther : TmsItemsHolder); class procedure RegisterInMarshal(aMarshal: TJSONMarshal); class procedure RegisterInUnMarshal(aMarshal: TJSONUnMarshal); end;//TmsItemsHolder {$Else TmsItemsHolder_intf} // implementation {$IfNDef TmsItemsHolder_uses_impl} // uses System.TypInfo {$Define TmsItemsHolder_uses_impl} {$Else TmsItemsHolder_uses_impl} // TmsItemsHolder constructor TmsItemsHolder.Create; begin inherited; Assert(f_Items = nil); f_Items := TmsItemsList.Create; end; destructor TmsItemsHolder.Destroy; begin FreeAndNil(f_Items); inherited; end; function TmsItemsHolder.pm_GetItems: TmsItemsList; begin if (f_Items = nil) then f_Items := TmsItemsList.Create; Result := f_Items; end; procedure TmsItemsHolder.pm_SetItems(aValue: TmsItemsList); var l_Item : TmsItem; begin if (f_Items <> nil) then f_Items.Clear; if (aValue <> nil) then for l_Item in aValue do begin if (f_Items = nil) then f_Items := TmsItemsList.Create; f_Items.Add(l_Item); end;//for l_Shape in aValue end; procedure TmsItemsHolder.Assign(anOther : TmsItemsHolder); begin Self.Items := anOther.Items; end; class procedure TmsItemsHolder.RegisterItemsLike(aLambda: TmsRttiFieldLambda); var l_Field : TRttiField; begin for l_Field in TRttiContext.Create.GetType(Self).GetFields do if (l_Field.Visibility = mvPrivate) then if (l_Field.Name = 'f_Items') then begin aLambda(l_Field); Exit; end;//l_Field.Name = 'f_Items' Assert(false, 'Не найдено поля для Items'); end; class procedure TmsItemsHolder.RegisterInMarshal(aMarshal: TJSONMarshal); begin RegisterItemsLike( procedure (aField: TRttiField) var l_FieldName : String; begin l_FieldName := aField.Name; aMarshal.RegisterConverter(Self, l_FieldName, function (Data: TObject; Field: String): TListOfObjects var l_Item: TmsItem; l_Index: Integer; begin Assert(Field = l_FieldName); SetLength(Result, (Data As TmsItemsHolder).Items.Count); l_Index := 0; for l_Item in (Data As TmsItemsHolder).Items do begin Result[l_Index] := l_Item.toObject; Inc(l_Index); end;//for l_Item end );//aMarshal.RegisterConverter end );//RegisterItemsLike end; class procedure TmsItemsHolder.RegisterInUnMarshal(aMarshal: TJSONUnMarshal); begin RegisterItemsLike( procedure (aField: TRttiField) var l_FieldName : String; begin l_FieldName := aField.Name; aMarshal.RegisterReverter(Self, l_FieldName, procedure (Data: TObject; Field: String; Args: TListOfObjects) var l_Object: TObject; l_Holder : TmsItemsHolder; l_ItemI : TmsItem; begin Assert(Field = l_FieldName); l_Holder := Data As TmsItemsHolder; Assert(l_Holder <> nil); for l_Object in Args do begin if Supports(l_Object, TmsItem, l_ItemI) then l_Holder.Items.Add(l_ItemI) else raise Exception.Create(l_Object.ClassName + ' не поддерживает нужный интерфейс'); end//for l_Object end );//aMarshal.RegisterReverter end );//RegisterItemsLike end; {$EndIf TmsItemsHolder_uses_impl} {$EndIf TmsItemsHolder_intf} {$EndIf TmsItemsHolder_uses_intf}
msDiagramm.pas:
unit msDiagramm; interface uses {$Include msItemsHolder.mixin.pas} , FMX.Graphics, System.SysUtils, System.Types, System.UITypes, msShape, msPointCircle, System.Classes, FMX.Objects, msRegisteredShapes, FMX.Dialogs, System.JSON, msCoreObjects, msSerializeInterfaces, msInterfacedNonRefcounted, msInterfacedRefcounted ; type ImsDiagramm = interface ['{59F2D068-F06F-4378-9ED4-888DFE8DFAF2}'] function toObject: TObject; function Get_Name: String; property Name: String read Get_Name; end;//ImsDiagramm TmsItemsHolderParent = TmsInterfacedRefcounted{TmsInterfacedNonRefcounted}; TmsItem = ImsShape; {$Include msItemsHolder.mixin.pas} TmsDiagramm = class(TmsItemsHolder, ImsDiagramm, ImsShapeByPt, ImsShapesController) // - Выделяем интерфейс ImsObjectWrap. // Смешно - если TmsDiagramm его реализет НАПРЯМУЮ, то всё хорошо. // А если через ImsSerializable, то - AV. // Про это можно писать отдельную статью. private [JSONMarshalled(False)] FCurrentClass: RmsShape; [JSONMarshalled(False)] FCurrentAddedShape: ImsShape; [JSONMarshalled(False)] FCanvas: TCanvas; [JSONMarshalled(True)] fName: String; private procedure DrawTo(const aCanvas: TCanvas); function CurrentAddedShape: ImsShape; procedure BeginShape(const aStart: TPointF); procedure EndShape(const aFinish: TPointF); function ShapeIsEnded: Boolean; class function AllowedShapes: TmsRegisteredShapes; procedure CanvasChanged(aCanvas: TCanvas); function ShapeByPt(const aPoint: TPointF): ImsShape; procedure RemoveShape(const aShape: ImsShape); function Get_Name: String; constructor CreatePrim(anImage: TImage; const aName: String); public class function Create(anImage: TImage; const aName: String): ImsDiagramm; procedure ResizeTo(anImage: TImage); procedure ProcessClick(const aStart: TPointF); procedure Clear; procedure Invalidate; procedure AllowedShapesToList(aList: TStrings); procedure SelectShape(aList: TStrings; anIndex: Integer); property Name: String read fName write fName; function CurrentShapeClassIndex: Integer; procedure Serialize; procedure DeSerialize; procedure Assign(const anOther : TmsDiagramm); property CurrentClass: RmsShape read FCurrentClass write FCurrentClass; end;//TmsDiagramm implementation uses {$Include msItemsHolder.mixin.pas} , msMover, msCircle, msDiagrammMarshal ; {$Include msItemsHolder.mixin.pas} const c_FileName = '.json'; class function TmsDiagramm.AllowedShapes: TmsRegisteredShapes; begin Result := TmsRegisteredShapes.Instance; end; procedure TmsDiagramm.AllowedShapesToList(aList: TStrings); var l_Class: RmsShape; begin aList.Clear; for l_Class in AllowedShapes do if l_Class.IsForToolbar then aList.AddObject(l_Class.ClassName, TObject(l_Class)); end; function TmsDiagramm.CurrentShapeClassIndex: Integer; begin Result := AllowedShapes.IndexOf(FCurrentClass); end; procedure TmsDiagramm.SelectShape(aList: TStrings; anIndex: Integer); begin if (anIndex < 0) then CurrentClass := TmsRegisteredShapes.Instance.First else CurrentClass := RmsShape(aList.Objects[anIndex]); end; procedure TmsDiagramm.Serialize; begin TmsDiagrammMarshal.Serialize(Self.Name + c_FileName, self); end; procedure TmsDiagramm.ProcessClick(const aStart: TPointF); begin if ShapeIsEnded then // - мы НЕ ДОБАВЛЯЛИ примитива - надо его ДОБАВИТЬ BeginShape(aStart) else EndShape(aStart); end; procedure TmsDiagramm.BeginShape(const aStart: TPointF); begin assert(CurrentClass <> nil); FCurrentAddedShape := CurrentClass.Create(TmsMakeShapeContext.Create(aStart, Self)); if (FCurrentAddedShape <> nil) then begin Items.Add(FCurrentAddedShape); if not FCurrentAddedShape.IsNeedsSecondClick then // - если не надо SecondClick, то наш примитив - завершён FCurrentAddedShape := nil; Invalidate; end; // FCurrentAddedShape <> nil end; procedure TmsDiagramm.Clear; begin if (f_Items <> nil) then f_Items.Clear; Invalidate; end; class function TmsDiagramm.Create(anImage: TImage; const aName: String): ImsDiagramm; begin Result := CreatePrim(anImage, aName); end; constructor TmsDiagramm.CreatePrim(anImage: TImage; const aName: String); begin inherited Create; FCurrentAddedShape := nil; FCanvas := nil; ResizeTo(anImage); FCurrentClass := AllowedShapes.First; fName := aName; end; function TmsDiagramm.Get_Name: String; begin Result := FName; end; procedure TmsDiagramm.ResizeTo(anImage: TImage); begin if (anImage <> nil) then begin anImage.Bitmap := TBitmap.Create(Round(anImage.Width), Round(anImage.Height)); CanvasChanged(anImage.Bitmap.Canvas); end;//anImage <> nil end; procedure TmsDiagramm.CanvasChanged(aCanvas: TCanvas); begin FCanvas := aCanvas; Invalidate; end; function TmsDiagramm.CurrentAddedShape: ImsShape; begin Result := FCurrentAddedShape; end; procedure TmsDiagramm.Assign(const anOther : TmsDiagramm); begin inherited Assign(anOther); Self.Name := anOther.Name; Self.Invalidate; end; procedure TmsDiagramm.DeSerialize; begin Clear; try TmsDiagrammMarshal.DeSerialize(Self.Name + c_FileName, Self); except on EFOpenError do Exit; end;//try..except end; procedure TmsDiagramm.DrawTo(const aCanvas: TCanvas); var l_Shape: ImsShape; begin aCanvas.BeginScene; try Assert(f_Items <> nil); for l_Shape in f_Items do l_Shape.DrawTo(TmsDrawContext.Create(aCanvas)); finally aCanvas.EndScene; end; // try..finally end; procedure TmsDiagramm.EndShape(const aFinish: TPointF); begin assert(CurrentAddedShape <> nil); CurrentAddedShape.EndTo(TmsEndShapeContext.Create(aFinish, Self)); FCurrentAddedShape := nil; Invalidate; end; procedure TmsDiagramm.Invalidate; begin if (FCanvas <> nil) then begin FCanvas.BeginScene; try FCanvas.Clear(TAlphaColorRec.Null); DrawTo(FCanvas); finally FCanvas.EndScene; end;//try..finally end;//FCanvas <> nil end; function TmsDiagramm.ShapeIsEnded: Boolean; begin Result := (CurrentAddedShape = nil); end; function TmsDiagramm.ShapeByPt(const aPoint: TPointF): ImsShape; var l_Shape: ImsShape; l_Index: Integer; begin Result := nil; for l_Index := f_Items.Count - 1 downto 0 do begin l_Shape := f_Items.Items[l_Index]; if l_Shape.ContainsPt(aPoint) then begin Result := l_Shape; Exit; end; // l_Shape.ContainsPt(aPoint) end; // for l_Index end; procedure TmsDiagramm.RemoveShape(const aShape: ImsShape); begin Assert(f_Items <> nil); f_Items.Remove(aShape); end; end.
msDiagramms.pas:
unit msDiagramms; interface uses {$Include msItemsHolder.mixin.pas} , msDiagramm, System.Types, FMX.Objects, System.Classes, msCoreObjects, msWatchedObjectInstance ; type TmsItemsHolderParent = TmsWatchedObject; TmsItem = ImsDiagramm; {$Include msItemsHolder.mixin.pas} TmsDiagramms = class(TmsItemsHolder) private [JSONMarshalled(True)] f_CurrentDiagramm : Integer; [JSONMarshalled(False)] f_Image: TImage; function pm_GetCurrentDiagramm: TmsDiagramm; public constructor Create(anImage: TImage; aList: TStrings); procedure ProcessClick(const aStart: TPointF); procedure Clear; procedure SelectShape(aList: TStrings; anIndex: Integer); procedure AllowedShapesToList(aList: TStrings); procedure ResizeTo(anImage: TImage); procedure AddDiagramm(anImage: TImage; aList: TStrings); function CurrentDiagrammIndex: Integer; procedure SelectDiagramm(anIndex: Integer); function CurrentShapeClassIndex: Integer; procedure Serialize; procedure DeSerialize; property CurrentDiagramm: TmsDiagramm read pm_GetCurrentDiagramm; procedure Assign(anOther: TmsDiagramms); end;//TmsDiagramms implementation uses {$Include msItemsHolder.mixin.pas} , System.SysUtils, msDiagrammsMarshal, msRegisteredShapes ; {$Include msItemsHolder.mixin.pas} // TmsDiagramms constructor TmsDiagramms.Create(anImage: TImage; aList: TStrings); begin inherited Create; f_Image := anImage; AddDiagramm(anImage, aList); end; function TmsDiagramms.pm_GetCurrentDiagramm: TmsDiagramm; begin Result := Items[f_CurrentDiagramm].toObject As TmsDiagramm; end; procedure TmsDiagramms.AddDiagramm(anImage: TImage; aList: TStrings); var l_D : ImsDiagramm; begin l_D := TmsDiagramm.Create(anImage, 'Диаграмма №' + IntToStr(Items.Count + 1)); Items.Add(l_D); f_CurrentDiagramm := Items.IndexOf(l_D); if (aList <> nil) then aList.Add(l_D.Name); // aList.AddObject(l_D.Name, TObject(f_CurrentDiagramm)); end; function TmsDiagramms.CurrentDiagrammIndex: Integer; begin Result := f_CurrentDiagramm; end; procedure TmsDiagramms.SelectDiagramm(anIndex: Integer); begin if (anIndex < 0) OR (anIndex >= Items.Count) then Exit; f_CurrentDiagramm := anIndex; CurrentDiagramm.Invalidate; end; const c_FileName = 'All.json'; procedure TmsDiagramms.DeSerialize; begin TmsDiagrammsMarshal.DeSerialize(c_FileName, self); // CurrentDiagramm.DeSerialize; end; procedure TmsDiagramms.Assign(anOther: TmsDiagramms); var l_D : ImsDiagramm; begin inherited Assign(anOther); for l_D in Items do begin (l_D.toObject As TmsDiagramm).ResizeTo(f_Image); (l_D.toObject As TmsDiagramm).CurrentClass := TmsRegisteredShapes.Instance.First; end;//for l_D Self.f_CurrentDiagramm := anOther.CurrentDiagrammIndex; CurrentDiagramm.Invalidate; end; procedure TmsDiagramms.ProcessClick(const aStart: TPointF); begin CurrentDiagramm.ProcessClick(aStart); end; procedure TmsDiagramms.Clear; begin CurrentDiagramm.Clear; end; procedure TmsDiagramms.SelectShape(aList: TStrings; anIndex: Integer); begin CurrentDiagramm.SelectShape(aList, anIndex); end; procedure TmsDiagramms.Serialize; begin TmsDiagrammsMarshal.Serialize(c_FileName, self); // CurrentDiagramm.Serialize; end; procedure TmsDiagramms.AllowedShapesToList(aList: TStrings); begin CurrentDiagramm.AllowedShapesToList(aList); end; procedure TmsDiagramms.ResizeTo(anImage: TImage); begin CurrentDiagramm.ResizeTo(anImage); end; function TmsDiagramms.CurrentShapeClassIndex: Integer; begin Result := CurrentDiagramm.CurrentShapeClassIndex; end; end.
Ну и сериализация:
TmsMarshal.mixin.pas:
{$IfNDef TmsMarshal_uses_intf} // interface {$Define TmsMarshal_uses_intf} // uses JSON, Data.DBXJSONReflect, msMarshalPrim {$Else TmsMarshal_uses_intf} {$IfNDef TmsMarshal} // http://programmingmindstream.blogspot.ru/2014/12/generic-2.html {$Define TmsMarshal} TmsMarshal = class(TmsMarshalPrim) // - шаблонизируем, ибо мы скоро будем сериализовать и другие классы. public class procedure Serialize(const aFileName: string; const aDiagramm: TClassToSerialize); class procedure DeSerialize(const aFileName: string; const aDiagramm: TClassToSerialize); end;//TmsMarshal {$Else TmsMarshal} // implementation {$IfNDef TmsMarshal_uses_impl} // uses SysUtils, msCoreObjects, msStringList {$Define TmsMarshal_uses_impl} {$Else TmsMarshal_uses_impl} // TmsMarshal class procedure TmsMarshal.DeSerialize(const aFileName: string; const aDiagramm: TClassToSerialize); var l_StringList: TmsStringList; l_D : TClassToSerialize; begin l_StringList := TmsStringList.Create; try l_StringList.LoadFromFile(aFileName); l_D := UnMarshal.Unmarshal(TJSONObject.ParseJSONValue(l_StringList.Text)) As TClassToSerialize; try aDiagramm.Assign(l_D); finally FreeAndNil(l_D); end;//try..finally finally FreeAndNil(l_StringList); end;//try..finally end; class procedure TmsMarshal.Serialize(const aFileName: string; const aDiagramm: TClassToSerialize); var l_Json: TJSONObject; l_StringList: TmsStringList; begin l_StringList := TmsStringList.Create; try l_Json := nil; try l_Json := Marshal.Marshal(aDiagramm) as TJSONObject; l_StringList.Add(l_Json.toString); finally FreeAndNil(l_Json); end;//try..finally l_StringList.SaveToFile(aFileName); finally FreeAndNil(l_StringList); end;//try..finally end; {$EndIf TmsMarshal_uses_impl} {$EndIf TmsMarshal} {$EndIf TmsMarshal_uses_intf}
Ну и тесты:
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/?at=MS-6_AddTestToMindStream
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/TestmsSerializeController.pas?at=MS-6_AddTestToMindStream
https://bitbucket.org/ingword/mindstream/src/18a744357c57406fc89a5e5498d01394abe6cb4c/Tests/Module/msParametrizedShapeTestSuite.pas?at=MS-6_AddTestToMindStream
Вряд ли кому-то понравится. Я знаю.
Комментариев нет:
Отправить комментарий