How can I postpone the "rendering" of my DataObject while dragging and dropping a Winforms cross process

I have an object which, although it has a textual representation (i.e. it can be stored in a string of about 1000 printed characters), is expensive to generate. I also have a tree control that shows "summaries" of objects. I want to drag these objects not only into my application, but also into other applications that accept CF_TEXT or CF_UNICODETEXT, after which the text view is inserted at the target point.

I was thinking about deferring the "rendering" of the textual representation of my object so that it only runs when the object is dropped or pasted. However, it seems that Winforms eagerly calls the GetData () method at the start of the drag, which causes an excruciating multi-second delay at the start of the drag.

Is there a way to guarantee that GetData () will only happen during a break? Alternatively, what is the correct mechanism for implementing this deferred logging mechanism in Winforms?

+4
source share
1 answer

After some research, I was able to figure out how to do this without using the IDataObject COM interface (with all its FORMATETC gunk). I thought this might be of interest to others in the same predicament, so I wrote my decision. If this can be done more cleverly, I am all eyes / ears!

The System.Windows.Forms.DataObject class has this constructor:

 public DataObject(string format, object data) 

I called it this way:

 string expensive = GenerateStringVerySlowly(); var dataObject = new DataObject( DataFormats.UnicodeText, expensive); DoDragDrop(dataObject, DragDropEffects.Copy); 

The above code will put the string data in HGLOBAL during the copy operation. However, you can also call the constructor as follows:

 string expensive = GenerateStringVerySlowly(); var dataObject = new DataObject( DataFormats.UnicodeText, new MemoryStream(Encoding.Unicode.GetBytes(expensive))); DoDragDrop(dataObject, DragDropEffects.Copy); 

Instead of copying data through HGLOBAL this later call has the nice effect of copying data through (COM) IStream . There seems to be some magic in the .NET interop environment that handles the mapping between COM IStream and the .NET System.IO.Stream .

Now I needed to write a class that delayed the creation of the stream until the very last minute ( Lazy object pattern ), when the return target starts calling Length , Read , etc. It looks like this: (details edited for brevity)

 public class DeferredStream : Stream { private Func<string> generator; private Stream stm; public DeferredStream(Func<string> expensiveGenerator) { this.generator = expensiveGenerator; } private Stream EnsureStream() { if (stm == null) stm = new MemoryStream(Encoding.Unicode.GetBytes(generator())); return stm; } public override long Length { get { return EnsureStream().Length; } } public override long Position { get { return EnsureStream().Position; } set { EnsureStream().Position = value; } } public override int Read(byte[] buffer, int offset, int count) { return EnsureStream().Read(buffer, offset, count); } // Remaining Stream methods elided for brevity. } 

Please note that expensive data is only generated when the EnsureStream method EnsureStream called for the first time. This does not happen until the return target begins to IStream data in IStream . Finally, I changed the call code to:

 var dataObject = new DataObject( DataFormats.UnicodeText, new DeferredStream(GenerateStringVerySlowly)); DoDragDrop(dataObject, DragDropEffects.Copy); 

That was exactly what I needed to do the job. However, I rely on good target behavior. An incorrect drop in goals that would implicitly cause, say, the Read method, say, will lead to an expensive operation occurring sooner.

+3
source

Source: https://habr.com/ru/post/1401714/


All Articles