A year ago, our company faced the task of writing an application for importing data from Excel in C #, including using the clipboard and drag'n'drop. Excel when copying to the buffer puts there data in several formats. Some of them are standard type CF_TEXT, CF_CSV, etc. However, if you need to deal with merged cells and other joys, you may need to access directly the Range object, which has been dragged or copied. For this you need to use the CF_LINKSOURCE format and the IMoniker interface lying in it. How to do this, read under habrakatom.
In theory, everything is simple as in the fairy tale about Kashchei:
Range lies in
IMoniker ,
IMoniker in
IStream ,
IStream in
IDataObject ,
IDataObject in Clipboard. This is exactly what the method called from client code will look like:
public static Range GetRange ()
{
IDataObject dataObject = System.Windows.Forms.Clipboard . GetDataObject () as IDataObject ;
IStream iStream = IStreamFromDataObject (dataObject);
IMoniker compositeMoniker = IMonikerFromIStream (iStream);
return RangeFromCompositeMoniker (compositeMoniker);
}
There is only one nuance here: we do not need
System.Windows.Forms.IDataObject , returned by the
Clipboard .
GetDataObject (), and
System.Runtime.InteropServices.ComTypes.IDataObject . Fortunately, this is solved by a simple cast.
Getting an
IStream from
IDataObject is also easy. Please note that among a couple dozen formats placed on the clipboard by Excel, we need CF_LINKSOURCE.
private const string CF_LINKSOURCE_ID = "Link Source" ;
private static IStream IStreamFromDataObject ( IDataObject dataObject)
{
STGMEDIUM medium;
FORMATETC formatEtc = new FORMATETC ();
formatEtc. cfFormat = ( short ) System.Windows.Forms.DataFormats . GetFormat ( CF_LINKSOURCE_ID ). Id ;
formatEtc. dwAspect = DVASPECT . DVASPECT_CONTENT ;
formatEtc. lindex = - 1 ;
formatEtc. ptd = new IntPtr ( 0 );
formatEtc. tymed = TYMED . TYMED_ISTREAM ;
dataObject. GetData ( ref formatEtc, out medium);
return Marshal . GetObjectForIUnknown (medium. unionmember ) as IStream ;
}
So,
iStream received. Now you need to pull out of it
IMoniker . Here came the first nuance:
iStream needs to be rewound to the beginning. Otherwise,
OleLoadFromStream will return
STG_E_READFAULT . By the way,
OleLoadFromStream must be imported from ole32.dll.
Pinvoke.net will help us in this. The only thing in our code is to replace the result returned by it from int to
HRESULT , described there.
private static IMoniker IMonikerFromIStream ( IStream iStream)
{
iStream. Seek ( 0 , 0 , IntPtr . Zero );
Guid guid = Marshal . GenerateGuidForType ( typeof ( stdole . IUnknown ));
object obj;
if ( ole32 . OleLoadFromStream (iStream, ref guid, out obj))
return obj as IMoniker ;
else
return null ;
}
There is
iMoniker ! Further in theory, everything should be very simple. We
call the BindMoniker function from ole32.dll, into which we transfer
IMoniker and the
Range class
Guid , and at the output we get
Range . In fact, however, instead we get the error
MK_E_NOOBJECT . The point here is this. There are several types of monikers: File Moniker, Item Moniker, Composite Moniker and so on. In our case from the clipboard we get a monoquer of the type Composite. Which combines the other two - File and Item. The first points to the
Workbook , and the second to the
Range inside the
Workbook . To form compound monikers Excel, as we see, is able. But disassemble - no. Well, will have to help him.
private static Range RangeFromCompositeMoniker ( IMoniker compositeMoniker)
{
List < IMoniker > monikers = SplitCompositeMoniker (compositeMoniker);
if (monikers. Count != 2 )
throw new ApplicationException ( "Invalid moniker" );
IBindCtx bindctx;
if (! ole32 . CreateBindCtx ( 0 , out bindctx) || bindctx == null )
throw new ApplicationException ( "Can't create bindctx" );
object obj;
Guid workbookGuid = Marshal . GenerateGuidForType ( typeof ( Workbook ));
monikers[ 0 ]. BindToObject (bindctx, null , ref workbookGuid, out obj);
Workbook workbook = obj as Workbook ;
ExcelItemMonikerHelper helper = new ExcelItemMonikerHelper (monikers[ 1 ], bindctx);
return helper. GetRange (workbook);
}
private static List < IMoniker > SplitCompositeMoniker ( IMoniker compositeMoniker)
{
List < IMoniker > monikerList = new List < IMoniker >();
IEnumMoniker enumMoniker;
compositeMoniker. Enum ( true , out enumMoniker);
if (enumMoniker != null )
{
IMoniker [] monikerArray = new IMoniker [ 1 ];
IntPtr fetched = new IntPtr ();
HRESULT res;
while (res = enumMoniker. Next ( 1 , monikerArray, fetched))
{
monikerList. Add (monikerArray[ 0 ]);
}
return monikerList;
}
else
throw new ApplicationException ( "IMoniker is not composite" );
}
Using
SplitCompositeMoniker, we split the Composite moniker into File moniker and Item moniker, after which we simply call
BindToObject on the file
moniker and get a
Workbook object. And then we do the work for Excel and get a
Range object from Item moniker. For this, we wrote a helper. I will not give the code in the article, you can see it in the demo project. In essence, it parses the Displayer property of the moniker, pulls out the name of the sheet and the borders of the selected area and obtains the desired
Range from the
Workbook using standard
Microsoft.Office.Interop.Excel methods.
Here and the fairy tale is over. And who listened - well done and in the same way can get to the objects of any application that puts the moniker on the clipboard.
Demo project is
here . Despite the fact that the link leads to the code project, I did not copy the article, as you might think =) Simply, in the winter, there was still not enough karma to publish the article here, and not to forget the material, I published it there.
')
Thanks for attention!