[Webinar] Streamline your web hosting managementRegister Today

x
?
Solved

Word-close event with OLE and dp3.0

Posted on 1999-11-23
36
Medium Priority
?
2,638 Views
Last Modified: 2008-03-17
Hi!

I have an app written in Delphi 3.0 that controles an instance of word via OLE. Word does not exactly behave the way one could expect in some cases.
I posted this to the Office group also, but since nowone of you reads those group I add it here too. I'm aware that this is a question that does not really commit to any area. This is the closest though.

I want my word document to tell me when the user has clicked the x-button(or control menu Close[Ctrl+F4].
If I make a template and writes an event handler for the Close-event it works so neat with a normal document, but my OLE-document never sends me any close-event it just kills the window...
How am I supposed to make word let my app know when it is closing rather than when it has closed. I want to be able to pop up some own dialogs before the actual close of the document...

by the way. It's the samething with the dirty-flag. It's always considered to be "False" if it's an OLE-document...
why the hell is that?
What can I do about it? How do I write my own Dirty-flag for a document?
0
Comment
Question by:dr_gonzo
  • 13
  • 12
  • 11
36 Comments
 
LVL 7

Expert Comment

by:RBertora
ID: 2228061
What I think you want to do is enable word to control your application too..

So:

Word is and ole server
Your App is an ole server.

then in word : This document place this code:

Private Sub Document_Close()
    Set myobject = CreateObject("RobWord")
        myobject.Beep
    Set myobject = Nothing
End Sub

where "RobWord" is the name of your application/ole server.

As you can see I just created a beep function that can be called but you can put what ever code you want in the beep function...

Hope this is enough for you to do what you want, I don't have D3 so telling you how to make a ole server is out of the question but it is easy enough.

Rob ;-)

P.S.
as for all your dirty stuff, I don't even have a clue as to what your'e on about and I don't have the time to check it out :-(







0
 

Author Comment

by:dr_gonzo
ID: 2228340
Well no. I start and control a session of word. Start it with this code

procedure TJITWordClass.EmbeddFromFile(FilNamn : String);
var
      WordCreateInfo      : TCreateInfo;
begin
      WordCreateInfo.CreateType := ctFromFile;
      WordCreateInfo.ShowAsIcon := False;
      WordCreateInfo.ClassID := ProgIDToClassID('Word.Document');
      WordCreateInfo.FileName := FilNamn;
      FOLEContainer.CreateObjectFromInfo(WordCreateInfo);
      {
      The FOLEContainer points to a delphi-component of
      class TOLEContainer
      }
end;


I then link an template to the document. That template contain a
"Sub Document_Close()" with the code I want to be executed when my document is closed by the user. The problem I have is that there is no "Sub Document_Close()" ever executed by word when the user exit the document.

If I link the template to an ordinary document the event is run, but not when it's started as an OLE-doc.
I want Word to send my application a message (with SendMessage) so my app can ask if I want to save the word doc as a file, then word should on my request save the document to a given file name and then continue with it's closing.

regards,
Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2228551
Gettting the olecontainer to sendmessage to your app is probably never going to happen.

I have always used the direct ole approach instead of using the oleconatainer object:


uses comobj{D4},OleAuto{D3};

var  WordApp : Variant;
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise


using this connection to word I have done every thing I ever wanted to with word atuomation.. with this my previous comment will work : ie the onclose event of the document object will be triggered.

Rob ;-)
P.S.
Tell me if you want to use this approach, I can help you with it.





0
Receive 1:1 tech help

Solve your biggest tech problems alongside global tech experts with 1:1 help.

 
LVL 3

Expert Comment

by:vladika
ID: 2228660
Take a look on Demos\ActiveX\Olectnrs\olemdi.dpr demo.
In short it has form and olecontainer on the form. And it uses form's OnCloseQuery event to ask user 'Save changes to object?'.
If user click 'Yes' then it uses OleContainer.SaveAsDocument method to save modified object.

0
 
LVL 7

Expert Comment

by:RBertora
ID: 2228766
Hmm very impressive, I'll get back to you shortly..
Rob ;-)
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2228854
Ok I modified that demo, the following code works like a charm for the X button, but not so good for alt F4.
you have a play with it perhaps it is sufficient for your needs.

Rob;-)


  public
    { Public declarations }
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

  end;

var
  MainForm: TMainForm;

implementation

uses MDIChild, about;

{$R *.DFM}

procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  Handled := False;
// 273 click on X
// 257 alt F4
  if (Msg.Message = 273) or (Msg.Message = 257) then
  begin
    showmessage('Do your processing here');
  end;
(*
  Note that several messages 257 are sent so you should make
  sure only one instance of your processing gets run
  I am sure you can figure that out using a boolean variable
  and some strategically timed changing of that variable
*)
end;
0
 
LVL 3

Expert Comment

by:vladika
ID: 2228931
I think that the handler of OnCloseQuery fires in all cases (when you press 'X' button or when you press Alt+F4 or when you select 'Close' from menu).
What is purpose of AppMessage procedure?
From where should I call it?

0
 
LVL 7

Expert Comment

by:RBertora
ID: 2228945
From the first form your application loads.

 -check it out.
Rob ;-)
0
 
LVL 3

Expert Comment

by:vladika
ID: 2229049
I guess we speak about different things.
Why you don't want to use OnCloseQuery event?
From which point in the first form specifically should I call your procedure, i.e how should I modify demo to call your procedure?
P.S.
I use D5. Maybe in D3 there is such procedure in this demo.
0
 
LVL 3

Expert Comment

by:vladika
ID: 2230508
I checked D3 demos.
In the D3 this demo is under
Delphi 3\Demos\OleCtnrs\olemdi.dpr
0
 

Author Comment

by:dr_gonzo
ID: 2230638
Ohh thanks.

I'm sorry guys(and maybe girls...),
but I think I have missed to tell
you something important. I have
the AllowInPlace-property set to
False, because I need Word to be
running "for it self". If you take
the demo mentioned above and change
the AllowInPlace property to False
you'll se word getting on top of the
demo app. What I want to know is,
how do you know when the users clicks
the X-button in word, not in the MDI-
child of my app. Cause when word is
closed but the OLE-container still
active my saving-stuff is to late to
run, cause it needs word...
Actually I don't want the word's X,
There can be lots of documents there,
I want to know when my documents X
are clicked.

I want to trap the X-click in Word.
What I tried whas words
"Sub Docuument_Close()". It's executed
when a document is closed and the
document has that procedure or any
template linked to it.
In that VBA-procedure I use
SendMessage myprogramhWnd, etc etc
so that my programs WndProc knows
when it happens, it then should
execute the SaveCommand in word and
then go back to word VBA-script for
exiting (Sub Document_Close) and
allow the exit of the document and
then everythings fine.

This way works with ordinary documents.
It's just that the bloody
"Sub Document_Close"-VBA-event-handler
don't execute when started as an
OLE document, only "ordinary" doc's
Anyway around this?

\Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2230675
Vladika: I got confused between you adn drgonzo, I thought gonzo used that demo to create his app.. sorry.

GONZO: Because you are not using inplace : I REPEAT an earlier comment as it seems you missed it or didn't try it:

TRY THIS:

uses comobj{D4},OleAuto{D3};

var  WordApp : Variant;
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise


using this connection to word I have done every thing I ever wanted to with word atuomation.. with this my previous comment will work : ie THE ONCLOSE EVEN OF THE DOCUMENT OBJECT WILL BE TRIGGERED!!


Rob ;-)
P.S.
Tell me if you want to use this approach, I can help you with it.

0
 

Author Comment

by:dr_gonzo
ID: 2230703
Yes Rob.
I use that approach already.
just that I never get the
"Private Sub Document_Close()"
or
the AutoClose-macro to trigger.
OnClose???
what's that...
yeah plz help me just with that bit...

I'll check you code now.
keep in touch...

\Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2230734
Gonzo:

 ignore the TMainForm.AppMessage bit of code as it was only relevant to that demo vladika was talking about.


NOTE:

Remember to place your
  Sub Document_Close() //wordbasic code
int the THISDOCUMENT file (not normal file)
and it should work!


Rob;-)
   
0
 

Author Comment

by:dr_gonzo
ID: 2230763
Hmm well it doesn't.
I wonder what I'm doing wrong.
I have tried both ThisDocument
and having it in a template that my doc use. I call it let's call it myTempl.dot

Forget the OLE for a sec. If I go to word as usual and start a new document and then choose Tools->Templates and add-ons
(not sure that's the correct name,
i don't have the english version of
word)
and and choose myTempl.dot as template for the document.
When i close the document the AutoClose-macro or Sub document_close runs...

But not when I started it via OLE...

I don't know what the hell I'm doing wrong.
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2230839
I am using the following:
Microsoft word 97 SR-2
Delphi 4 professional

in wordVBA there are 3 windows,

top left window
bottom left window
right main window

in the top left there is a tree structure:

Normal
  Microsoft Word Objects
    ThisDocument
  Modules
    NewMacros

double click on ThisDocument (not just single click - double click)

now in the main window have this:

Private Sub Document_Close()
    MsgBox ("hello")
End Sub

only that nothing else.

in your delphi app do the following:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls,comobj;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  WordApp : Variant;
implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin

  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise
  WordApp.Documents.Add;
end;

end.


That opens word or connects to it, clicking X on the document will display the message box.

If that does not work on your machine then make sure your word version is the same as mine, if that fails get delphi 4 professional. (because it most definately works)

Rob ;-)

0
 

Author Comment

by:dr_gonzo
ID: 2230864
I check you exact code, and I think Iäm on to something.
I don't use the exact way you do cause it's rather slow using a variant,
so I do the same stuff via the type library.
Rewriting my hole code seems pretty messy to do right now.
But it sure gives a hint about whats wrong...

I'll keep in touch
\Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2230891
If you are using this method:
  WordApp := CoApplication_.Create;

This also works on my machine.

Rob ;-)

0
 
LVL 3

Accepted Solution

by:
vladika earned 2400 total points
ID: 2230955
BTW when you close document you can process this event in the Delphi.
See example

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComObj, ActiveX;

const
  WordDocEventIID: TGUID = '{000209F6-0000-0000-C000-000000000046}';

type

  TForm1 = class;

// this class dispatch events from word
  TWordDocumentEventDispatcher = class(TObject, IUnknown, IDispatch)
  private
    FOwner: TForm1;
    FRefCount : Integer;
  protected
    { IUnknown }
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    { IDispatch }
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  public
    constructor Create(AOwner: TForm1);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FDispatcher: TWordDocumentEventDispatcher;
    FConnection: Integer;
    FDoc: Variant;
    procedure OnClose;
    procedure Connect;
    procedure Disconnect;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TWordDocumentEventDispatcher }
constructor TWordDocumentEventDispatcher.Create(AOwner: TForm1);
begin
  FOwner := AOwner;
  FRefCount := 1;
end;

function TWordDocumentEventDispatcher.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
  begin
    Result := S_OK;
    Exit;
  end;
  if IsEqualIID(IID, WordDocEventIID) then
  begin
    GetInterface(IDispatch, Obj);
    Result := S_OK;
    Exit;
  end;
  Result := E_NOINTERFACE;
end;

function TWordDocumentEventDispatcher._AddRef: Integer;
begin
  FRefCount := FRefCount + 1;
  Result := FRefCount;
end;

function TWordDocumentEventDispatcher._Release: Integer;
begin
  FRefCount := FRefCount -1;
  Result := FRefCount;
end;

function TWordDocumentEventDispatcher.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Count := 0;
  Result:= S_OK;
end;

function TWordDocumentEventDispatcher.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
  Pointer(TypeInfo) := nil;
  Result := E_NOTIMPL;
end;

function TWordDocumentEventDispatcher.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWordDocumentEventDispatcher.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params;
  VarResult, ExcepInfo, ArgErr: Pointer): HResult;
begin
  try
    case DispID of
     -1: Exit;  // DISPID_UNKNOWN
      6: FOwner.OnClose;
    end;
    Result := S_OK;
  except
    Result := S_FALSE;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  WordApp : Variant;
  Doc: Variant;
begin
  Disconnect;
  try
    WordApp := GetActiveOleObject('Word.Application.8');
  except on exception do
    WordApp := CreateOleObject('Word.Application.8');
  end;

  WordApp.Visible := True;
  WordApp.Activate;
  FDoc := WordApp.Documents.Open('C:\My documents\Test.doc');
  Connect;
end;

procedure TForm1.Connect;
begin
  InterfaceConnect(FDoc, WordDocEventIID, FDispatcher, FConnection);
end;

procedure TForm1.Disconnect;
begin
  InterfaceDisconnect(FDoc, WordDocEventIID, FConnection);
  FDoc := Unassigned;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FDispatcher := TWordDocumentEventDispatcher.Create(Self);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Disconnect;
  FDispatcher.Free;
end;

procedure TForm1.OnClose;
begin
  MessageBox(0, 'OnClose', '', 0); // this procedure will be executed when you close document in the Word
end;
0
 

Author Comment

by:dr_gonzo
ID: 2231052
Vladika.

This seems really intresting. I have problems making it work though. It doesn't compile cause Delphi 3.0 sais
"undeclared identifier: InterfaceConnect"

though I include the OleCtrl.pas
that beacuse the function is private.

Could you mail me the example source .zip-ed?
a.ohman@jit.se

This looks intresting. If the event ever go from Word at all that is.
Cause till now it's really look like Word doesn't send any events.
But I'd like to try this one out anyway.

\Dr. Gonzo
0
 
LVL 3

Expert Comment

by:vladika
ID: 2231110
Ok. I sent it.
Just in case I wrote source for this functions.

procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
  const Sink: IUnknown; var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  Connection := 0;
  if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
    if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
      CP.Advise(Sink, Connection);
end;

procedure InterfaceDisconnect(const Source: IUnknown; const IID: TIID;
  var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  if Connection <> 0 then
    if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
      if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
        if Succeeded(CP.Unadvise(Connection)) then Connection := 0;
end;

0
 
LVL 7

Expert Comment

by:RBertora
ID: 2231132
Just watching now as you two seem to have things cooking nicely :-)

But I'd be very surprised if you can get D5 to work with D3.

Rob ;-)
0
 
LVL 3

Expert Comment

by:vladika
ID: 2231136
Ok. I sent it.
Just in case I wrote source for this functions.

procedure InterfaceConnect(const Source: IUnknown; const IID: TIID;
  const Sink: IUnknown; var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  Connection := 0;
  if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
    if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
      CP.Advise(Sink, Connection);
end;

procedure InterfaceDisconnect(const Source: IUnknown; const IID: TIID;
  var Connection: Longint);
var
  CPC: IConnectionPointContainer;
  CP: IConnectionPoint;
begin
  if Connection <> 0 then
    if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then
      if Succeeded(CPC.FindConnectionPoint(IID, CP)) then
        if Succeeded(CP.Unadvise(Connection)) then Connection := 0;
end;

0
 
LVL 3

Expert Comment

by:vladika
ID: 2231156
Sorry for reposting.
To: RBertora
Why not?
I do not use special D5 features, classes or functions. I have not D3 at hand just now. But when I will back to home (4 hours later) I will convert it to D3 certainly. :)


0
 
LVL 7

Expert Comment

by:RBertora
ID: 2231318
Hmm your code is very good Vladika, works well D4. (We W8C D3)

Remember though all of my stuff works well too.

For now I just wait and see what DR Gonzo says.

Rob ;-)
0
 

Author Comment

by:dr_gonzo
ID: 2231762
Works nice with D3 i think.
It's seems to be the correct solution.
BUt integrating it in to my source code will take me a couple of minutes :-)
I'll keep in touch as soon as I can.
Maybe tommorrow or the day after.

just one over course question if you would happen to know...

Rob: You set the caption on word like this: WordApp.Caption := 'MyWord...';

I need to do the same to my document.
Even do it's an unsaved file, to get things to work it would be nice to set the caption on my document to something else than word "Document 1".
Can I do this?

(this has got nothing to do with the point. I just ask if anyone of you know, cause it will make me evaluate the answer faster)

The both of you(Rob, Vladika) has helped with this problem, if it works, you'll get point for it the both of you.
The same question is asked on the office-part of EE, so you can have 600 each...

just need to get it to work first...

Thanks for all help so far
\Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2231823
changing doc captions:

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    WordApp := GetActiveOleObject('Word.Application');
  except on exception do
    WordApp := CreateOleObject('Word.Application');
  end;

  WordApp.Visible := True;
  WordApp.Caption := 'MyWord...';
  WordApp.WindowState := 1; //maximise
  WordApp.Documents.Add;

end;


procedure TForm1.Button2Click(Sender: TObject);
begin
  WordApp.ActiveDocument.ActiveWindow.Caption := 'hello';
end;


Rob;-)

By the way very very kind of you to give so many points I really would like mine to be in the delphi group :-)

0
 

Author Comment

by:dr_gonzo
ID: 2231896
I guess you both want the point in the delphi group.
I'll cancel the message in the office-group. Put some really stupid question here on the delphi group.
And give 600 points for it, and give them to you...
Then the both of you are proberly happy...

\Dr. Gonzo
0
 

Author Comment

by:dr_gonzo
ID: 2231899
Bye the way thanks for the caption-code.
I figured it out myself at the same time the mail arrived :-)

tnx again,
Dr. Gonzo
0
 
LVL 7

Expert Comment

by:RBertora
ID: 2231907
Gonzo I be happy with 100pts
Rob ;-)
0
 
LVL 3

Expert Comment

by:vladika
ID: 2232312
I think so. 600 pts is very much.
But I think you should not cancel this question. It should stay for history :)
How can we reduce cost of question?
0
 
LVL 3

Expert Comment

by:vladika
ID: 2232389
Also look at http://www.intac.com/~bly/com/resources/downloads.htm
"EventSinkImp is a utility that imports sink interfaces from COM server type libraries and automatically produces code for a Delphi non-visual component that publishes the sink methods as Delphi events."
0
 

Author Comment

by:dr_gonzo
ID: 2233663
1st. The information is well worth 600 points. Even 2*600 cause it's the diffrence of a working app and a nonworking app and I could never gotten this far without this help.

Vladika: is that link alright?
I can't go there...

and no I woun't cancel *this* question. I'll cancel the copy on the Office-group. There are no good answers there anyway. Not a single intelligent comment :-) users you know...

\Dr. Gonzo
0
 
LVL 3

Expert Comment

by:vladika
ID: 2234029
Check link now. Some time ago I couldnot open link too. But now it works.
0
 

Author Comment

by:dr_gonzo
ID: 2234085
yeah, works like a charm...

\Dr. Gonzo
0
 

Author Comment

by:dr_gonzo
ID: 2239874
Okey. Now my program is up and running.
Thanks to you guys.
Vladikas answer with source code was the best so you'll have the point here.

RBertora:
I'm adding a question in the delpgi group named "points for RBertora"
comment something and you'll get your points to.

thanks once more,
Dr. Gonzo

0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
The video will let you know the exact process to import OST/PST files to the cloud based Office 365 mailboxes. Using Kernel Import PST to Office 365 tool, one can quickly import numerous OST/PST files to Office 365. Besides this, the tool also comes…
Free Data Recovery software is an advanced solution from Kernel Tools to recover data and files such as documents, emails, database, media and pictures, etc. It supports recovery from physical & logical drive after a hard disk crash, accidental/inte…
Suggested Courses
Course of the Month11 days, 3 hours left to enroll

612 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question