Sunday, March 20, 2016
Back from TMS dayI am personally very happy with everything about the TMS day. It took 26 hours flying plus 6 hours in a car trip to Germany so I could speak for an hour, but it was completely worth it. There is still something magical about face to face communications, something that is lost in written words. I speak daily with tens of customers via email, I also write posts like this very one, and still there is something missing when you don't see the face of the human you are speaking to.
I also was happy about how the sessions worked: Instead of someone standing there and reading a powerpoint, the sessions were interactive and people in the room asked many interesting questions. This was how it was intended to be, and I'd like to think that we are all -the tms team and the people who assisted- happy about it. But of course we are also aware that things are never perfect, and a lot of stuff can be made better for the next time. You only get better if you correctly identify what can be made better first.
So let's stop with the good parts (which were a lot) and let's focus for a little in what can be improved. The main weak point, from my own impressions and from the impressions I got from the people I spoke in the breaks, seems to be that the sessions didn't go too much in depth in the technical part. This is kind of reasonable if you think about it: To cover an item in depth one hour isn't too much, and there was a *lot* of stuff that we wanted to cover. I could have spoken for a week about FlexCel and there would be still stuff left to be said.
The fact that the sessions were interactive didn't help either: It is hard to plan a presentation when you don't know in advance how much questions will be asked. In this point I must say that even if I was aware that the time in the actual presentation is much shorter than the time when you plan it, I still grossly miscalculated how much stuff I could fit in an hour, and how many interesting questions would appear. I presented just half of the stuff I had prepared and I feel bad about that.
And the last problem is about the technical differences between attendees. Being a TMS day, some people came there to hear in depth stuff about some specific TMS products and have an overview of the others. So some people were heavy FlexCel users while others hadn't heard about it. If you make the presentation too technical, the guys who came for the "overview" will get a good nap. If you make it too general, then the guys who eat FlexCel <#tags> and __ranges__ for breakfast will be bored.
So now, about the hard part: Solutions.
As said the main issue was time, and of course the obvious solution would be to do a "TMS week" not a "TMS day", but this is not really viable. We could show less stuff and focus more, and I think this is something to consider for next time. I also would like to have larger breaks because I think the breaks are the best part of any event. In them you get to speak casually with the guys doing the sessions, and also with other customers which are using the same solutions as you. And we get to speak casually with you. But it is a compromise: the larger the breaks you make, the less sessions you can make in one tms day. Even so, I would vote for longer breaks.
About the unpredictability of the sessions, we could make the sessions not interactive and leave all the questions for the end. But I don't think that is a great idea; in my opinion the greatest thing about the tms day is the interactivity. To watch a 1-hour lecture about something we could just upload a video to youtube and you could watch it from the comfort of your home. You could then ask the questions in the comments of the video. It does work, but it loses the magic. I much prefer to cover half of the themes and have a nice talk with the guys in the room, than to cover everything and not interact.
About the diversity of experience in the room, well, I don't see too much solution about that. I tried to do a mix of both general information and technical, but I ended up speaking more of the general parts than of the more technical stuff. And one thing I didn't know in advance is that most of the people in the room were using FlexCel so I could have skipped the introductory parts. I was surprised by that: I knew which products the people were using, but most people coming were using VCL subs or TMS all access, which doesn't gave me extra information. I honestly wasn't expecting so many FlexCel users, and even if that makes me very happy, I need to apologize: I'll try to make the presentation more technical the next time.
And finally, about all the stuff I couldn't say or expand because of the limited time: That bothers me a lot. There is so much stuff that I wanted to say and I couldn't! But at least I got a solution for this: I will be using the rest of this post to write some of the things I would have said if I had the time to expand even more in the questions that were asked. So without further delay, let's go down to business:
That Excel yellow warningOne of the questions raised when I was showing how to create Excel files with FlexCel is why when I opened them in Excel, Excel wasn't showing a warning that the file wasn't created with Excel. What I answered is that Excel doesn't do any check that the file is a "genuine" Excel file or anything like this. As long as the file conforms to the spec (and even many files which don't conform) Excel will be happy to open it.
There are two types of warnings that Excel might show when opening a file:
1)The "Red" warnings:
This is not a very common warning, and it happens when the file contains invalid data. You should never get this warning in files created with FlexCel, and if you do, you should contact us so we fix it.
2)The "Yellow" warnings:
This might be because the file was downloaded from the internet, because it has macros, or in general when the file is valid but there are some security concerns. The particular question was about the "file is downloaded from the internet" warning, and so this is what I will cover here. First of all, note that the warning isn't related to FlexCel, it just happens whenever you donwload a file from the internet, no matter if it was created with Excel, FlexCel or whatever, and it looks like this:
In the session I commented that the flag that triggers this warning is not really stored in the file, but in a separate hidden NTFS stream. But I didn't had the time to expand more into it. So today, I'll take the opportunity to dive a little bit deeper.
Firs of all, you can see the streams in a file by issuing this command in a command prompt:
The tools to manipulate the stream in DOS are a little bit limited, but you can see the stream content in notepad by doing:
But of course, this was a Delphi session, so we care more about on how to do it from Delphi itself. And it turns out it isn't difficult, as you can just delete the stream:
The "Do you want to save changes?" warning and Excel 2016Unable to leave a good thing alone, I spoke about other warning that I know many users care about (and you can already start to understand why 1 hour was never going to be enough to cover everything I wanted to cover). The warning dialog shows when you close the file immediately after opening it and without modifying it. The exact text of the warning varies with the type of file (xls or xlsx) and the Excel version, but normally looks like this:
Or sometimes like this:
It happens whenever you have formulas in your file and the Excel version the file declares it was saved with is different from the Excel version that you open the file with. What happens under the hood is that Excel recalculates the file when it was saved by an older version because the older version might have bugs in the calculated values. So then it offers to save the new recalculated values in the file, even if nothing did change. And it will also identify the new saved file as "Saved with the new Excel version" so it doesn't need to be recalculated again when you reopen it.
Again this is not specific to FlexCel: If you save a file with formulas in Excel 2010 and open it in Excel 2016, you will get a warning when closing the file.
Of course, FlexCel is not Excel 2010 or 2016, but we do have a property that allows us to "identify" the file as being created by an specific Excel version. So I showed first how the dialog appears in a simple file created with FlexCel. This happens because by default FlexCel identifies the file as created by an unknown Excel version, so it is always recalculated when you open it.
But then, I went to show on how you could make the warning disappear by setting the property:
xls.RecalcVersion := TRecalcVersion.Excel2016;
In short: There was a "January Update" of Excel 2016 which happened to introduce a bunch of new functions. (see https://support.office.com/en-us/article/What-s-new-in-Excel-2016-for-Windows-5fdb9208-ff33-45b6-9e08-1f5cdb3a6c73?ui=en-US&rs=en-US&ad=US
We were aware of this update, and since the functions introduced are probably the first useful new functions introduced since Excel 2007, we already had implemented recalculation for them all at the tms day timeframe (even if we hadn't yet released an update to the public). But what I hadn't realized is that this "January Update" also changed the recalculation id that must be saved with the file.
Files created with a "Pre-January-Update" Excel 2016 (or with FlexCel reporting as Excel 2016) would have a diffefent recalculation ID and trigger the save dialog when you opened them in "Post-January-Update" Excel 2016.
This was the reason the demo failed: FlexCel was still writing the "Pre-January-Update" ID into the file, but the Excel I used in the presentation had silently updated some days ago to the "January Update" and was recalculating the "old Excel 2016" file.
So we just released an update to FlexCel (6.7.16) which will:
1)Add full support for all the new functions in the Excel 2016 january update.
2)Identify the files saved with a RecalcID of Excel2016 with the "Post-January-2016" Id.
3)Add a new member "LatestKnownExcelVersion" to the TRecalcVersion enum. Now you can set:
xls.RecalcVersion := TRecalcVersion.LatestKnownExcelVersion;
Virtual DatasetsThis is one of the slides I had planned to show in the presentation, but which I didn't had time to:
And it was a shame, since many people contacted me after the session to inquire about this specific topic. So again, I will use the time I have in this blog post to expand on it:
As you might know if you are a FlexCelReport user, FlexCel can use any TDataSet, any TList
As you can see on the slide, FlexCel actually gets all of its data from 2 abstract classes: TVirtualDataTable and TVirtualDataTableState. FlexCel comes with specialized classes derived from them which implement it for a TDataSet, TList
Also mentioned in the slide there was an example project which creates specializations that allow you to use a TStringList as a datasource in your FlexCel Reports. You can get it here: http://www.tmssoftware.biz/flexcel/samples/stringlisttable.zip
To create a new datasource of FlexCel, you need to answer some questions like "how many records my datasource has?", or "what are the names of the columns for my datasource?". To do so, you need to create 2 different classes, descending from TVirtualDataTable and TVirtualDataTableState. Why 2 different classes? The difference between TVirtualDataTable and TVirtualDataTableState is that the first contains information which is static and can be used in different threads or different datasources in the same report without worries, while the second contains state information which changes for each dataset used, even if it the dataset is the same. FlexCel can keep a single copy of the TVirtualDataTable in memory and use it for all similar tables in a report, but it needs to create different TVirtualDataTableState classes for each table.
For example, a DataTable will have always the same columns and column names, no matter how many times it is used inside the same report. So the question: "what are the names of the columns for my datasource?" is answered in the TVirtualDataTable abstract class, by overriding the Get_ColumnCount, GetColumn, GetColumnName and GetColumnCaption methods.
For example, the TStringList datasource has a single column with the data (as it is just a list of strings). We will name this column "Data" and so the methods look like this:
function TStringListProvider.GetColumn(const columnName: UTF16String): Int32; begin if not SameText(columnName, 'DATA') then raise Exception.Create('Unknown Column: ' + columnName); Result := 0; end; function TStringListProvider.GetColumnCaption( const columnIndex: Int32): UTF16String; begin Result := GetColumnName(columnIndex); end; function TStringListProvider.GetColumnName( const columnIndex: Int32): UTF16String; begin Result := 'Data'; end; function TStringListProvider.Get_ColumnCount: Int32; begin Result := 1; end;
function TStringListStateProvider.Get_RowCount: Int32; begin Result := FStringList.Count; end;
function TStringListStateProvider.GetValue(const column: Int32): TReportValue; begin Result := FStringList[Position]; end; function TStringListStateProvider.GetValue(const row, column: Int32): TReportValue; begin Result := FStringList[row]; end;
function TStringListProvider.CreateState(const sort: UTF16String; const masterDetailLinks: TMasterDetailLinkArray; const splitLink: TSplitLink): TVirtualDataTableState; begin Result := TStringListStateProvider.Create(FStringList, self); end;
Of course this only shows the simplest virtual datatable wrapper that you can create. There are many other methods available for overriding in TVirthalDataTable and TVirtualDataTableState, which allow for advanced functionality. For example, if you want to do a lookup in the datasource with the <#lookup> tag, you might want to override the Lookup method (even when the default implementation is good). Some other functionality like DISTINCT might not be available if you don't override the corresponding method. But for most reports, what we covered here is enough.
The report designerAnother thing I got asked about is the report designer I had installed in Excel during the presentation. This is an Excel addin which appears as a new tab in the ribbon:
It allows you to design a report template in a simpler way, by dragging and dropping fields from a tag pane. The bad news is that it has been in its current state (not yet finished) for more than a year already, and I never seem to get the time to finish it. There are always more important things to do. But as I've been asked by many people in the tms day about the possibility to get a beta, I will do my best to get a beta released in a couple of weeks. Not all functionality will be working, but the basics should be there.
This blog post has not received any comments yet. Add a comment.
Tuesday, May 28, 2013
Introducing FlexCel 6
As you might have noticed, we've just released FlexCel 6 for Delphi with iOS support (FlexCel 6 for .NET is coming soon now and it will also have iOS support). We've taken our time to release it because we didn't want this to be a "kind of works in iOS" release, but to be a fully working, usable solution to deal with xls, xlsx, pdf and html files in iOS.
And I think we've gotten it right. FlexCel 6 can smoothly generate xls, xlsx, native pdf and html files, open xls and xlsx files, print and preview xls and xlsx, and all in iOS. To get the maximum performance and compatibility, the xls/x viewer uses direct CoreGraphics calls in iOS or OSX instead of FireMonkey canvas calls to render the files, in a way that they can show in your phone as they show in a desktop machine. All the page is rendered to an off-screen buffer, which is then shown in the completely FireMonkey-native and styleable TFlexCelPreviewer control. You get the best of both worlds: You have a native FireMonkey control that can interoperate nicely with the rest of the FireMonkey controls in your form, and can be styled as any other FireMonkey control, but under the hood, the control is doing native calls to iOS for rendering to get the best results.
And this isn't a cut-out version, almost everything you can do in your desktop machine you can do it on your phone. The only thing that you can't do in iOS is to read or write encrypted xlsx files, because adding crypto support would require that you declare crypto routines when submitting to the app store, and you would need to comply with all the export regulations. So by the moment we are leaving encryption out, but virtually everything else is in. (Encryption might come later as an optional, use-it-at-your-own-risk, addin). Of course there are limitations with respect to the Desktop version; in a phone you have less memory, a less capable CPU, and everything will go slower. But it works and works quite well. (We've spent also a lot of time optimizing memory usage in FlexCel 6, so it works better in phones, but also works better in your desktop machine).
Working with files in iOS
As explained above, if we don't count encryption and the slower cpu/less memory, FlexCel for iOS is virtually identical to FlexCel for Windows. It is the same code, the same calls, everything, and this makes porting simple. But there is a big difference: Working with files.
In iOS you can't read or write files outside the folder where your application is. Which makes the system much more secure (now this little game that you downloaded can't read all your mail and documents and send them to some server overseas), but on the other hand, makes it much more difficult to interoperate and share files between apps.
For FlexCel 6 we were not happy to just give you a library to read and write xls/x files, and so we've spent a lot of time investigating how to make the workflow simpler, how to workaround the bugs currently in XE4, and how to make your app interop with the others. So you can read an xls file from DropBox, modify it, and send it back to DropBox. Or email it.
And I think all the information we collected might be very useful not only for FlexCel users, but for everybody who needs to deal with files in iOS and Delphi.
So I will write a link to the two documents explaining how to deal with files in iOS here. Those two documents also come as expected when you install FlexCel: trial or registered.
1)Using FlexCel with iOS.
This is a conceptual document, and explains how to import and export the files from Delphi. Once again, while it is targeted to FlexCel users, most of the content applies even if you are not a FlexCel user.
This is a step-by-step tutorial showing how to start with an empty application and make it interoperate with the other apps in your phone. The full source code for the app created in the tutorial is available in the FlexCel trial or registered distributions.
Bonus track: Video of FlexCel 6 working in an iPhoneWhile FlexCel is focused mostly in the non visual stuff, here is an small example of how the FlexView application in the tutorial above works in an actual device:
FlexCel 6 working on an iPhone:
FlexCel 6 working on an iPad(*):
(*)Note: In the iPad example we are showing a chart. Currently, charts are rendered in xls, but not in xlsx.
This blog post has not received any comments yet. Add a comment.
Monday, March 05, 2012This post has been updated in Apr 2013 to reflect the new changes
I am really happy to say we have finally released our first version of FlexCel 5 for VCL/Firemonkey. It has been a huge lot of hard work, and even when there is still a lot of work to do, the biggest part is done. This version doesn't include the rendering and reporting engines, but that is no more than 20% of the total lines of code; the other 80% has already been delivered.
I still need to write a post about the technical stuff on how it was created (which I think is quite interesting), but today I want to speak about the release itself, how you can use it, what is changed and what is still missing.
What's included?As you might know, FlexCel is actually 3 products in one: A library for reading and writing Excel files, a reporting library to create Excel files by writing tags inside a template, and a rendering library that can print, preview and convert any Excel file to pdf, html or images.
While I would have loved to include everything in this first release, the reality is that it would have made no sense to make people who needs the library to read and write xls/x files wait until the rendering engine is finished. So we are launching things as soon as they are ready, and this first version is about the first of those products, the library to read and write xls/x files. The reporting and rendering engine are still being worked on, and will be released in other updates in the same way, as soon as they are ready.
We plan for a lot of releases in the coming months while we work towards full feature parity with FlexCel.NET. Below is a rough scheme of what is still missing:
Launch schedule:Update Apr 2013: FlexCel is now at 5.6.5, and it includes Full Recalculation, ApiMate, C++ builder support (XE2 or newer), Encrypted xls and xlsx files, improved documentation and demos, the stability fixes, and the rendering engine inclding pdf and html support. The only thing missing from the schedule is the reporting engine, which should be coming soon. Many things not planned like a FireMonkey viewer were added too.
v5.1 (estimated for the end of march):
It will focus in finishing the parts of the API that couldn't be ready for the first launch. Those include:
- Full recalculation support: We found some showstopper bugs and had to disable recalculation in v5.0, but it will be coming as soon as we can possibly fix the problem.
- APIMate: APIMate is a tool that tells you how to code an Excel sheet. You open an xls/xlsx file created with Excel in APIMate, and it will tell you the exact Delphi code you need to create that file. As with recalculation, APIMate is internally working and I hoped it would make the initial release, but it had some bugs (related to RTTI in initialized enums) that made us delay it.
- If possible, C++ Builder support: I am not 100% sure if this one is even possible, the truth is that Delphi produces completely bonked hpp headers for FlexCel that won't even compile. After some investigation headers could manually be fixed, but there are also bugs in the way Rad Studio returns records (C structs) to C++ builder, causing AVs when some records are returned. As said, we still need to investigate this in depth so I can't say much more until we do.
- Encrypted files xls and xlsx files: Support for encrypted files is already in the shipped 5.0 code, but not enabled since we need to code our own encryption algorithms like SHA1 and AES for it to work. At the moment the code just calls some abstract interfaces for SHA1 and AES and will not open encrypted files.
- Documentation and demos: We should be improving a lot in the documentation, providing F1 help, and adding many new demos. APIMate should help a lot with the learning curve too.
- Stability fixes: As with any first release, we expect to find many small issues that we will be fixing in this 5.1 release.
- Reporting Engine: A completely rewritten report engine that will allow a lot of new stuff in Reports. A converter from v3 reports to v5 reports will be included.
- Rendering Engine: Exporting xls/x files to pdf, html or images. Printing and previewing Excel files.
Also note that this is a tentative schedule, as mentioned at the start of this post, the idea is to release things as soon as they get ready to use, so we might get more releases if some parts prove more difficult so we don't keep the other parts waiting for that. Also final release numbers might be different, the only thing certain about version numbers is that when we achieve full FlexCel .NET parity, it will go to the number FlexCel .NET is at that moment.
After that we will resume a parallel schedule with FlexCel .NET, and releases from there are going to be simultaneous in both platforms.
Structural changesFlexCel v5 is a big change and completely independent from FlexCel v3. This means that you can (and probably should for a while if you have any legacy code) have both versions installed in parallel.
Where have my components gone? Update Apr 2013: FlexCel 5.6.5 has now one visual component, TFlexCelPreviewer, available for VCL and for FireMonkey.
The first thing that you will notice when you install FlexCel 5 is that no components are installed in the toolbar. You will still see FlexCelImport and FlexCelReport and all the v3 components if you have v3 installed, but no new ones. If you don't have v3, you won't see any new components at all.
The reason is simple, we have changed all non visual components to classes. "FlexCelImport" being a component didn't add anything to it and it created issues, like for example not being able to have 2 components of the same name registered in the palette. (so you wouldn't be able to have a v3 FlexCelReport and a v5 FlexCelReport installed at the same time, unless we renamed the v5 component as "FlexCelReport5" or other silly name). It also was problematic if you had say a console application and no form where to drop the component, and as said, having them be "components" didn't add much. So they are now classes instead.
This means that now, instead of dropping a FlexCelImport into a form, you would write the following code: (note that FlexCelImport changed to XlsFile, see "Class Architecture" below)
var xls: TXlsFile; begin xls := TXlsFile.Create(true); try DoSomething(xls); finally xls.Free; end; end;
If we focus in the main components (forgetting helper components like TTemplateStore or TFlxMemTable), the original v3 FlexCel architecture looked something like the following:
TExcelAdapter was an abstract class that provided an interface between an actual Excel file and the components doing the work. We originally provided two different implementations of TExcelAdapter: TXlsAdapter (native interface) and TOleAdapter (interface using OLE Automation). Then you could plug TFlexCelReport into a TXlsAdapter and have a report generated natively, or plug it to an OLE Adapter and have the report generated by OLE Automation.
This was a nice abstraction and it worked fine at the beginning (when we only had a FlexCelReport component), but with the introduction of FlexCelImport (a component that was originally designed to only read xls files as its name implies, but later was expanded to also write files) things got a little too complex.
As you can see in the diagram, you have an "Adapter" and a "FlexCelImport" class that do mostly the same. So most of the methods (but not all) in FlexCelImport, just call the same method in the ExcelAdapter engine. This meant not only a lot of redundant code (and redundancy is one of the main things that we want to avoid), but also a lot of confusion in users who didn't know what to use, if ExcelAdapter or FlexCelImport. We explained in the documentation that you should use FlexCelImport and not ExcelAdapter, but people kept using ExcelAdapter. And when people keeps doing the "wrong" thing despite what the docs say, this normally means not a problem in the docs or the users, but a deeper problem in the conceptual design of the code. This wasn't as intuitive as it could be.
The last problem with this architecture was that FlexCelImport was at the same level as FlexCelReport, so they couldn't "see" each other. The design was top-down, and the components at the top can only know about the components at the bottom. So in the places where FlexCelReport allowed hand-optimization of the code, it had to expose an Adapter component (the only thing it knew about) and not a FlexCelImport component. But you were supposed to use FlexCelImport to do manual modifications in the file, not the Adapters.
If we sit back and take a look from the distance, all the problems came from the fact that FlexCelImport was added as a separate layer over the Adapter components, and it didn't had to be that way. So, in v5 we only have one class to read and write Excel files, and it is at the bottom where everybody else can use them, as it should be. There is no more FlexCelImport in v5, and the scheme looks something like this:
Where now the abstract class to read and write is TExcelFile, and the native implementation of that class is TXlsFile. All the code you used to use with FlexCelImport, should use TXlsFile now instead.
Migration from v3 to v5The first thing to know when planning the migration is that, as mentioned earlier, you can have both versions installed at the same time. As this is a big change, it will help being able to migrate code part by part and at your own peace.
Migrating FlexCelImport code
Migrating FlexCelImport code shouldn't be difficult. You need to replace the TFlexCelImport component by TXlsFile class, but most methods remain the same or similar. Probably the most important change is that properties have been changed to Get/Set methods (because C++ builder can get very buggy when dealing with indexed properties), so instead of a property FlexCelImport.CellValue[row, col] you now have a TXlsFile.SetCellValue and TXlsFile.GetCellValue methods. Once you learn the little differences migration gets quite easy.
Update Apr 2013: APIMate for delphi and C++ Builder is already released.
Once we release APIMate, there will also be another option, that is creating the file in v3, opening it in APIMate, and get the v5 code.
Reports in v5 have a lot of new stuff, they are way more powerful now.
The main difference with the old reports is tags: we have unified them all. Now all tags are in the form <#tag>. This means you have to write <#table.field> instead of ##table##field, <#delete row> instead of ...delete row... and <#reportvar> instead of #.reportvar#.
We will be providing a tool to do the migration that should take care for most of it automatically, but you will probably want to look at the old reports anyway to make use of the new functionality. And as you can have both versions installed, you can go changing them one by one as you feel you can.
Update Apr 2013: TXlsxAdapter is already released, you can get it when you install the latest FlexCel 3 (currently 3.7) from the registered downloads page. It is recommended that you replace all your TXlsAdapters byTXlsxAdapters.
There is another tool, that while not literally a migration helper, will help building a bridge between v3 and v5 until we have finished the functionality in v5 and you had your time to migrate everything. If you look at the v3 class diagram above, you will see that we have an abstract "Adapter" class that serves as an engine where the other components talk. We had an OLEAdapter and a TXlsAdapter in v3, but what if we added a new adapter to the mix? A "TXlsxAdapter" that uses v5 to read the Excel files and can be used by v3 FlexCelImport and v3 FlexCelReport? As this adapter will use v5 to read and write to the file, it will be able to read and write xls and xlsx/xlsm files. And you will be able to read and write those files with the v3 components. While in the long term the idea is to move to v5 components, by using TXlsxAdapter you can keep using v3 and reading and writing xlsx files.
We will be shipping TXlsxAdapter this week, as a separate download.
ThanksTo round up this post, I would like to end up with a personal note. I would like to personally thank you for all the support and the amazing response FlexCel 5 got. There were times during the days after the launch where I would have say 20 emails to answer, I would answer 10, look again, and I would now have 24 emails to answer!
I would also want to apologize for all the delays v5 got, nobody more than me would have liked to have it sooner, but the reality is that coding an Excel clone is a lot of work, much more than it would appear once you start looking at all the little details, and we are doing it twice (once for .NET and once for VCL).
So when at the beginning of this post I said I was really happy with this launch I really mean it, it has been a lot of work and I am glad it is finally out there. Well, if you excuse me now I am going back to work to get that 5.1 release ready.
Previous | Next | Index