Document attachments?

General TRichView support forum. Please post your questions here
GNUMatrix
Posts: 5
Joined: Sat Feb 24, 2007 10:32 pm

Document attachments?

Post by GNUMatrix »

Hi there...

Wondering if I've overlooked something or if it is just not there. I've been testing tRichView as a means towards a generic document manager with the data stored in a database. I've got it working the way that I want, with the text and images, etc stored in a DB2 table BLOB field, and the data moves about without much trouble.

I would like to be able to attach files to these documents as well. Just like adding an inline attachment to an e-mail. This would allow me to store Excel or PDF files in the same document store, and have these accessible without the need for shared drives and so on. Would be nice if you double-clicked on the attachment that it did the usual windows thing and launched the related application or asked for what application to run if necessary.

If anyone here has used Lotus Notes, you know what I'm getting at here, this makes for a very handy document repository with a lot more uses.

Any suggestions would be most appreciated.
Thanks!
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

1. Use your own format to store multiple files, including RVF and attachments

or

2. Convert attachments to text (for example, using base64 encoding) and save them in DocProperties property.
GNUMatrix
Posts: 5
Joined: Sat Feb 24, 2007 10:32 pm

Post by GNUMatrix »

Sergey Tkachenko wrote:1. Use your own format to store multiple files, including RVF and attachments

or

2. Convert attachments to text (for example, using base64 encoding) and save them in DocProperties property.
Thanks for the reply.

Good suggestions, I think I understand how that would work.

Could I raise this as a feature request, though? Neither option is really ideal from a user-interface perspective. Not the simplest from a developer perspective either, given that there may be a number of files, and that the files themselves would not have any kind of marking in the actual document, just a separately managed list.

I guess I'm thinking of the RichView component as a more generic container that I would like to include more things into in the same WYSIWYG type of interface. To be able to inline embed other files without having any knowledge of their formats or having to do anything special to handle any number of such files. I'm not talking about importing or converting to RVF format for the user to see, but rather just "putting" the file(s) there, stored in the same place as the surrounding text, so that it follows the document. Internally, Base64 encoding I'm sure could be used and ultimately storing them in the docproperties may well be how it is managed internally, but the interface needs to tie into this a bit differently than just having an available list of files loosely associated with the document.

I'm not sure if this has been described as well as it needs to be. Lotus Notes was the best example, but there probably aren't a lot of those users here doing this kind of thing. I can send along some screenshots if it would help?

Thanks!
Andrew.
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

Andrew, I think there is more easier and flexible way. You could write a small component that derived from TControl and named TDocRepository. It should has a property named Attachments: TOwnedCollectionItem. "Attachments" class TCollectionItem should be another class named "Attachment" with a property named "FileName": String and "Contents": TStrings;


You could then add you attachments to this CollectionItem and finally add this component into TRichView. Later when you want read the attachments you simply need to find you TDocRepository component by FindComponent and read it's CollectionItem.

I have the same problem as you and I'm thinking over this solution. I need that for embeding a TOC (Table of Content) and lot's of user comments into TRichView.

Sorry for my bad english
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Or you can search for thirdparty components implementing ZIP or other type of archive that can contain several files.
You can store RVF and attachments in the same archive file (you can rename extension from .zip to your own one), plus you get a compression.
GNUMatrix
Posts: 5
Joined: Sat Feb 24, 2007 10:32 pm

Post by GNUMatrix »

mamouri wrote:Andrew, I think there is more easier and flexible way. You could write a small component that derived from TControl and named TDocRepository. It should has a property named Attachments: TOwnedCollectionItem. "Attachments" class TCollectionItem should be another class named "Attachment" with a property named "FileName": String and "Contents": TStrings;


You could then add you attachments to this CollectionItem and finally add this component into TRichView. Later when you want read the attachments you simply need to find you TDocRepository component by FindComponent and read it's CollectionItem.

I have the same problem as you and I'm thinking over this solution. I need that for embeding a TOC (Table of Content) and lot's of user comments into TRichView.

Sorry for my bad english
Hmmm...

No problem with the language. Maybe you could clarify this part... "add this component into richview"? I see that you can add Delphi controls into the text stream, but can this be done at any point in the document by an enduser?

So if this TDocRepository component also had an image property, could that image be rendered at the point in the document that the file was attached? Maybe with the filename underneath it? If the document were to be printed, it would simply include the icon in the document.

Here's a picture of what I'd think it could look like...

Image
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

Actually for my use it should not show a picture to end user. But Yes you could inherit the component from TGraphicControl. I think in your case the component should named TRVAttachedDoc. This TRVAttachedDoc should has 3 property

1- FileName: String
2- Content: TFileStream
3- FileType: TRVFileType

type
TRVFileType = set of (ftDOC, ftXLS);

Then everytime you want attach a file you can create an instance of TRVAttachedDoc and fill 3 property. Component also should read from a RES file, DOC and XLS icon.

I think it's the most ideal solution and very easy to perform.

Sorry again for my bad english :roll:
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

I forgot to mention that Compnent Paint method should paint corresponding file icon in regard to FileType property and the FileName.
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

>> but can this be done at any point in the document by an enduser?
One of the most important advantages of TRichView is that you could insert component in every position of the document. Actually all component derived from TControls can add to TRichView. These controls could easily save into TRichView and load later from rvf files.

Actually Controls Items are completely similar to other controls in TRichView.

You could also add the feature to TRVAttachedDoc component so if user doubleclick on the component it open corresponding file viewer.

If you wrote such component, I think it would be nice if you share it with community or send it to Sergey to place it in Resources page. 8)
http://www.trichview.com/resources/
GNUMatrix
Posts: 5
Joined: Sat Feb 24, 2007 10:32 pm

Post by GNUMatrix »

mamouri wrote:>> but can this be done at any point in the document by an enduser?
One of the most important advantages of TRichView is that you could insert component in every position of the document. Actually all component derived from TControls can add to TRichView. These controls could easily save into TRichView and load later from rvf files.

Actually Controls Items are completely similar to other controls in TRichView.

You could also add the feature to TRVAttachedDoc component so if user doubleclick on the component it open corresponding file viewer.

If you wrote such component, I think it would be nice if you share it with community or send it to Sergey to place it in Resources page. 8)
http://www.trichview.com/resources/
Sounds like we're headed in the right direction.

I'm not so great at writing components (never written one, even after all these years as a Delphi programmer), but I guess I could give it a shot. I would do the filetypes thing a bit differently, by storing an icon itself. I used PDF and XLS files as samples, but really I don't know and don't really care what file they attach, I would just get the icon from the file at the time it was attached.
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

I would just get the icon from the file at the time it was attached.
I did not think this is right approach. Assume user attached a DOC file when he has installed Microsoft Office 2003. A week later he/she may upgrade to Microsoft Office 2007. Then we he/she open the document, will see icon of old Microsoft Word 2003.

I think when should acquire the icon from file types he/she currently has in his/her computer.
Last edited by mamouri on Mon Feb 26, 2007 5:20 pm, edited 1 time in total.
mamouri
Posts: 63
Joined: Sat Aug 19, 2006 5:06 am

Post by mamouri »

Actually writing this component is not so hard. I did not have enough time But I wrote a very simple component:

Code: Select all

unit RVAttachedFile;

interface

uses
  SysUtils, Classes, Controls, Windows, Registry, ShellAPI, Variants, Graphics;

type
  PHICON = ^HICON;
  TRVAttachedFile = class(TGraphicControl)
  private
    FFileName: String;
    FContent: TStringList;
    FIcon: TIcon;
    function GetFileExt: String;
  protected
    procedure Paint; override;
    procedure GetAssociatedIcon(FileName: TFilename; PLargeIcon, PSmallIcon: PHICON);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property FileName: String read FFileName write FFileName;
    property Content: TStringList read FContent write FContent;
    property FileExt: String read GetFileExt;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TRVAttachedFile]);
end;

{ TRVAttachedFile }

constructor TRVAttachedFile.Create(AOwner: TComponent);
var
  LargeIcon, SmallIcon: HICON;
begin
  inherited;
  Width := 40;
  Height := 50;

  // Get file icon
  FIcon := TIcon.Create;
  GetAssociatedIcon(FileExt, @LargeIcon, @SmallIcon);
  if SmallIcon <> 0 then
    FIcon.Handle := LargeIcon;
end;

destructor TRVAttachedFile.Destroy;
begin

  inherited;
end;

function TRVAttachedFile.GetFileExt: String;
begin
  Result := ExtractFileExt(FFileName);
end;

procedure TRVAttachedFile.Paint;
begin
  Canvas.Draw(0, 0, FIcon);
  inherited;
end;

// From here: http://www.swissdelphicenter.ch/torry/showcode.php?id=218
procedure TRVAttachedFile.GetAssociatedIcon(FileName: TFilename; PLargeIcon, PSmallIcon: PHICON);
var
  IconIndex: SmallInt;  // Position of the icon in the file
  Icono: PHICON;       // The LargeIcon parameter of ExtractIconEx
  FileExt, FileType: string;
  Reg: TRegistry;
  p: Integer;
  p1, p2: PChar;
  buffer: array [0..255] of Char;

Label
  noassoc, NoSHELL; // ugly! but I use it, to not modify to much the original code :(
begin
  IconIndex := 0;
  Icono := nil;
  // ;Get the extension of the file
  FileExt := UpperCase(ExtractFileExt(FileName));
  if ((FileExt = '.EXE') and (FileExt = '.ICO')) or not FileExists(FileName) then
  begin
    // If the file is an EXE or ICO and exists, then we can
    // extract the icon from that file. Otherwise here we try
    // to find the icon in the Windows Registry.
    Reg := nil;
    try
      Reg := TRegistry.Create;
      Reg.RootKey := HKEY_CLASSES_ROOT;
      if FileExt = '.EXE' then FileExt := '.COM';
      if Reg.OpenKeyReadOnly(FileExt) then
        try
          FileType := Reg.ReadString('');
        finally
          Reg.CloseKey;
        end;
      if (FileType <> '') and Reg.OpenKeyReadOnly(FileType + '\DefaultIcon') then
        try
          FileName := Reg.ReadString('');
        finally
          Reg.CloseKey;
        end;
    finally
      Reg.Free;
    end;

    // If there is not association then lets try to
    // get the default icon
    if FileName = '' then goto noassoc;

    // Get file name and icon index from the association
    // ('"File\Name",IconIndex')
    p1 := PChar(FileName);
    p2 := StrRScan(p1, ',');
    if p2 = nil then
    begin
      p         := p2 - p1 + 1; // Position de la coma
      IconIndex := StrToInt(Copy(FileName, p + 1, Length(FileName) - p));
      SetLength(FileName, p - 1);
    end;
  end; //if ((FileExt  '.EX ...

  // Try to extract the small icon
  if ExtractIconEx(PChar(FileName), IconIndex, Icono^, PSmallIcon^, 1) <> 1 then
  begin
    noassoc:
    // That code is executed only if the ExtractIconEx return a value but 1
    // There is not associated icon
    // try to get the default icon from SHELL32.DLL

    FileName := 'C:\Windows\System\SHELL32.DLL';
    if not FileExists(FileName) then
    begin  //If SHELL32.DLL is not in Windows\System then
      GetWindowsDirectory(buffer, SizeOf(buffer));
      //Search in the current directory and in the windows directory
      FileName := FileSearch('SHELL32.DLL', GetCurrentDir + ';' + buffer);
      if FileName = '' then
        goto NoSHELL; //the file SHELL32.DLL is not in the system
    end;

    // Determine the default icon for the file extension
    if (FileExt = '.DOC') then IconIndex := 1
    else if (FileExt = '.EXE') or (FileExt = '.COM') then IconIndex := 2
    else if (FileExt = '.HLP') then IconIndex := 23
    else if (FileExt = '.INI') or (FileExt = '.INF') then IconIndex := 63
    else if (FileExt = '.TXT') then IconIndex := 64
    else if (FileExt = '.BAT') then IconIndex := 65
    else if (FileExt = '.DLL') or (FileExt = '.SYS') or (FileExt = '.VBX') or
      (FileExt = '.OCX') or (FileExt = '.VXD') then IconIndex := 66
    else if (FileExt = '.FON') then IconIndex := 67
    else if (FileExt = '.TTF') then IconIndex := 68
    else if (FileExt = '.FOT') then IconIndex := 69
    else
      IconIndex := 0;
    // Try to extract the small icon
    if ExtractIconEx(PChar(FileName), IconIndex, Icono^, PSmallIcon^, 1) <> 1 then
    begin
      //That code is executed only if the ExtractIconEx return a value but 1
      // Fallo encontrar el icono. Solo "regresar" ceros.
      NoSHELL:
      if PLargeIcon = nil then PLargeIcon^ := 0;
      if PSmallIcon = nil then PSmallIcon^ := 0;
    end;
  end; //if ExtractIconEx

  if PSmallIcon^ = 0 then
  begin //If there is an small icon then extract the large icon.
    PLargeIcon^ := ExtractIcon(Self.Parent.Handle, PChar(FileName), IconIndex);
    if PLargeIcon^ = Null then
      PLargeIcon^ := 0;
  end;
end;

end.
I think you could develop this component to get the behavior you want from that. I did not test it. Working whit this component should be as easy as this:

Code: Select all

const Attachment = 'C:\A.DOC';
var
  AFile: TRVAttachedFile;
begin
  AFile := TRVAttachedFile.Create(Self);
  with AFile do begin
    Hide;
    Parent := Self;
    Content.LoadFromFile(Attachment);
    FileName := Attachment;

    RVE.InsertControl(AFile);
  end;
As I said I did not test this and I'm not sure it work ok!
GNUMatrix
Posts: 5
Joined: Sat Feb 24, 2007 10:32 pm

RVAttachedFile

Post by GNUMatrix »

Ok....

That's a pretty good start. I don't know if I'll get to this today, but if I have any luck I'll post the results here.

As far as the icons go, there are different ways to do it. I'd rather store the original icon and display a new one if available, but lots of files could be attached for applications that don't exist on a particular client computer. Use no icon or the icon from the originating application? In the case of JPG files or other image files, the icon might be a tiny thumbnail, which isn't a bad thing and would be more descriptive than the icon for whatever application is installed to handle JPG files.

I guess I'll give it a try and see what works best.

Thanks so much!
Andrew.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Only text files can be stored in TStringList.

I suggest the following changes:

Code: Select all

unit RVAttachedFile; 

interface 

uses 
  SysUtils, Classes, Controls, Windows, Registry, ShellAPI, Variants, Graphics; 

type 
  PHICON = ^HICON; 
  TRVAttachedFile = class(TGraphicControl) 
  private 
    FFileName: String; 
    [color=blue]FContent: TMemoryStream;[/color] 
    FIcon: TIcon; 
    function GetFileExt: String; 
    [color=blue]procedure WriteContent(Stream: TStream);
    procedure ReadContent(Stream: TStream);[/color]
  protected 
    procedure Paint; override; 
    procedure GetAssociatedIcon(FileName: TFilename; PLargeIcon, PSmallIcon: PHICON); 
    [color=blue]procedure DefineProperties(Filer: TFiler); override;[/color]
  public 
    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; 
    [color=blue]function LoadFromFile(const AFileName: String): Boolean;
    property Content: TMemoryStream read FContent;[/color] 
  published 
    property FileName: String read FFileName write FFileName; 
    property FileExt: String read GetFileExt; 
  end; 

procedure Register; 

implementation 

procedure Register; 
begin 
  RegisterComponents('Samples', [TRVAttachedFile]); 
end; 

{ TRVAttachedFile } 

constructor TRVAttachedFile.Create(AOwner: TComponent); 
begin 
  inherited; 
  Width := 40; 
  Height := 50; 

  FIcon := TIcon.Create; 
  [color=blue]FContent := TMemoryStream.Create;[/color]
end; 

destructor TRVAttachedFile.Destroy; 
begin 
  [color=blue]FIcon.Free;
  FContent.Free;[/color]
  inherited; 
end; 

function TRVAttachedFile.GetFileExt: String; 
begin 
  Result := ExtractFileExt(FFileName); 
end; 

procedure TRVAttachedFile.Paint; 
begin 
  Canvas.Draw(0, 0, FIcon); 
  inherited; 
end; 

// From here: http://www.swissdelphicenter.ch/torry/showcode.php?id=218 
procedure TRVAttachedFile.GetAssociatedIcon(FileName: TFilename; PLargeIcon, PSmallIcon: PHICON); 
var 
  IconIndex: SmallInt;  // Position of the icon in the file 
  Icono: PHICON;       // The LargeIcon parameter of ExtractIconEx 
  FileExt, FileType: string; 
  Reg: TRegistry; 
  p: Integer; 
  p1, p2: PChar; 
  buffer: array [0..255] of Char; 

Label 
  noassoc, NoSHELL; // ugly! but I use it, to not modify to much the original code :( 
begin 
  IconIndex := 0; 
  Icono := nil; 
  // ;Get the extension of the file 
  FileExt := UpperCase(ExtractFileExt(FileName)); 
  if ((FileExt = '.EXE') and (FileExt = '.ICO')) or not FileExists(FileName) then 
  begin 
    // If the file is an EXE or ICO and exists, then we can 
    // extract the icon from that file. Otherwise here we try 
    // to find the icon in the Windows Registry. 
    Reg := nil; 
    try 
      Reg := TRegistry.Create; 
      Reg.RootKey := HKEY_CLASSES_ROOT; 
      if FileExt = '.EXE' then FileExt := '.COM'; 
      if Reg.OpenKeyReadOnly(FileExt) then 
        try 
          FileType := Reg.ReadString(''); 
        finally 
          Reg.CloseKey; 
        end; 
      if (FileType <> '') and Reg.OpenKeyReadOnly(FileType + '\DefaultIcon') then 
        try 
          FileName := Reg.ReadString(''); 
        finally 
          Reg.CloseKey; 
        end; 
    finally 
      Reg.Free; 
    end; 

    // If there is not association then lets try to 
    // get the default icon 
    if FileName = '' then goto noassoc; 

    // Get file name and icon index from the association 
    // ('"File\Name",IconIndex') 
    p1 := PChar(FileName); 
    p2 := StrRScan(p1, ','); 
    if p2 = nil then 
    begin 
      p         := p2 - p1 + 1; // Position de la coma 
      IconIndex := StrToInt(Copy(FileName, p + 1, Length(FileName) - p)); 
      SetLength(FileName, p - 1); 
    end; 
  end; //if ((FileExt  '.EX ... 

  // Try to extract the small icon 
  if ExtractIconEx(PChar(FileName), IconIndex, Icono^, PSmallIcon^, 1) <> 1 then 
  begin 
    noassoc: 
    // That code is executed only if the ExtractIconEx return a value but 1 
    // There is not associated icon 
    // try to get the default icon from SHELL32.DLL 

    FileName := 'C:\Windows\System\SHELL32.DLL'; 
    if not FileExists(FileName) then 
    begin  //If SHELL32.DLL is not in Windows\System then 
      GetWindowsDirectory(buffer, SizeOf(buffer)); 
      //Search in the current directory and in the windows directory 
      FileName := FileSearch('SHELL32.DLL', GetCurrentDir + ';' + buffer); 
      if FileName = '' then 
        goto NoSHELL; //the file SHELL32.DLL is not in the system 
    end; 

    // Determine the default icon for the file extension 
    if (FileExt = '.DOC') then IconIndex := 1 
    else if (FileExt = '.EXE') or (FileExt = '.COM') then IconIndex := 2 
    else if (FileExt = '.HLP') then IconIndex := 23 
    else if (FileExt = '.INI') or (FileExt = '.INF') then IconIndex := 63 
    else if (FileExt = '.TXT') then IconIndex := 64 
    else if (FileExt = '.BAT') then IconIndex := 65 
    else if (FileExt = '.DLL') or (FileExt = '.SYS') or (FileExt = '.VBX') or 
      (FileExt = '.OCX') or (FileExt = '.VXD') then IconIndex := 66 
    else if (FileExt = '.FON') then IconIndex := 67 
    else if (FileExt = '.TTF') then IconIndex := 68 
    else if (FileExt = '.FOT') then IconIndex := 69 
    else 
      IconIndex := 0; 
    // Try to extract the small icon 
    if ExtractIconEx(PChar(FileName), IconIndex, Icono^, PSmallIcon^, 1) <> 1 then 
    begin 
      //That code is executed only if the ExtractIconEx return a value but 1 
      // Fallo encontrar el icono. Solo "regresar" ceros. 
      NoSHELL: 
      if PLargeIcon = nil then PLargeIcon^ := 0; 
      if PSmallIcon = nil then PSmallIcon^ := 0; 
    end; 
  end; //if ExtractIconEx 

  if PSmallIcon^ = 0 then 
  begin //If there is an small icon then extract the large icon. 
    PLargeIcon^ := ExtractIcon(Self.Parent.Handle, PChar(FileName), IconIndex); 
    if PLargeIcon^ = Null then 
      PLargeIcon^ := 0; 
  end; 
end;

[color=blue]procedure FContent.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('FileContent', ReadContent, WriteContent, FContent.Size>0);
end;


procedure TRVAttachedFile.ReadContent(Stream: TStream);
var v: Integer;
begin
  FContent.SetSize(0);
  Stream.ReadBuffer(v, sizeof(v));
  if v>0 then
    FContent.CopyFrom(Stream, v);
end;

procedure TRVAttachedFile.WriteContent(Stream: TStream);
var v: Integer;
begin
  v := FContent.Size;
  Stream.WriteBuffer(v, sizeof(v));
  FStream.Position := 0;
  Stream.CopyFrom(FContent, v)
end;

function TRVAttachedFile.LoadFromFile(const AFileName: String): Boolean;
var fs: TFileStream;
  LargeIcon, SmallIcon: HICON; 
begin
  Result := False;
  try
    fs := TFileStream.Create(AFileName, fmOpenRead);
    try
      FStream.SetSize(0);
      FStream.CopyFrom(fs, fs.Size);
    finally
      fs.Free;
    end;
    FFileName := AFileName;
    FFileExt := AnsiUpperCase(ExtractFileExt(AFileName));
    GetAssociatedIcon(FileExt, @LargeIcon, @SmallIcon); 
    if LargeIcon <> 0 then 
      FIcon.Handle := LargeIcon
    else
      FIcon.Handle := SmallIcon;
    Result := True;
  except;
  end;
end;[/color]
end.

Code: Select all

const Attachment = 'C:\A.DOC'; 
var 
  AFile: TRVAttachedFile; 
begin 
  AFile := TRVAttachedFile.Create(nil); 
  with AFile do begin 
    Hide; 
    Parent := Self; 
    if not LoadFromFile(Attachment) then begin
      Free;
      exit;
    end; 
  end;
  RVE.InsertControl(AFile); 
end;
I typed this code in browser. Not tested.
Sergey Tkachenko
Site Admin
Posts: 17559
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

As for the code searching icons... I do not think that "shell32" branch is really necessary, but it is rather strange.
First, it looks in c:\windows\system\. Next, it looks in windows dir and in the current dir. It will never find it if it is located in d:\winxp\system32.
(Searching for file with the known name in the known directory using FileSearch? Hmm...). The file must be located in GetSystemDirectory.
Post Reply