По мотивам:
Про "примеси", шаблоны и 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
Вряд ли кому-то понравится. Я знаю.
Комментариев нет:
Отправить комментарий