Friday, August 05, 2016

A Persistent Queue with C#

This is just a bit of programming trivia.

I wanted to run a queue of tasks each of which could take a while to complete. Given that the program could be terminated before all tasks had been run then I needed to store any remaining tasks until a later opportunity to process them. Also I wanted to run the task queue on a background thread and be able to add to the queue of tasks from the UI thread.

Fortunately .NET has an inbuilt ConcurrentQueue<T> in System.Collections.Concurrent that can be accessed from multiple threads without issue. All that needed to be added was some code to persist uncompleted queue tasks. In the code below I am using the .NET port of SQLite but a flat file would work fine as would any other database.

As I was using a database I wanted to differentiate between queued tasks already in the relevant table and any added from the UI thread. [Not of course forgetting to delete tasks in the database that are completed.] So a little class to represent a task – the task itself being just a string in this instance.

class QueuedTask {     private string task;     private bool fromDatabase = false;     public QueuedTask(string task)     {         this.task = task;     }     public QueuedTask(string task, bool fromDatabase)     {         this.task = task;         this.fromDatabase = fromDatabase;     }     public bool InDatabase     {         get { return fromDatabase; }     }     public string Task     {         get { return task; }     } }
Creating an instance of my PersistentQueue Class starts a timer that loads any saved tasks into the queue on another thread so as not to block the UI thread.. The main program form FormClosing() event calls the class SaveTasks() method that saves any remaining tasks in the queue to the database.

class PersistentQueue {     public ConcurrentQueue<QueuedTask> TaskQueue = new ConcurrentQueue<QueuedTask>();     private string dbPath = "";     private Timer readTimer = new Timer();     private const int startDelay = 10000;     public PersistentQueue(string dbPath)     {         this.dbPath = dbPath;         readTimer.Interval = startDelay;         readTimer.Elapsed += (sender, e) => loadSavedTasks();         readTimer.Start();     }     public void SaveTasks()     {         readTimer.Close();         if (!TaskQueue.IsEmpty)         {             try             {                 SQLiteConnection newCon = new SQLiteConnection(dbPath);                 newCon.Open();                 SQLiteCommand mCommand = new SQLiteCommand("Insert Into Tasks (Task) Values(@Task)", newCon);                 mCommand.Parameters.Add(new SQLiteParameter("@Task"));                 QueuedTask mTask;                 while (TaskQueue.TryDequeue(out mTask))                 {                     if (!mTask.InDatabase)                     {                         mCommand.Parameters["@Task"].Value = mTask.Task;                         mCommand.ExecuteNonQuery();                     }                 }                 mCommand.Dispose();                 newCon.Close();             }             catch (Exception ex)             {             }         }     }     private async void loadSavedTasks()     {         readTimer.Stop();         await Task.Run(() => loadTasks());         // this is where you might start a new Timer to execute any tasks sitting         // in the queue - again using await Task.Run() and to periodically check the         // queue for new entries after the current queue has been emptied     }     private void loadTasks()     {         try         {             SQLiteConnection newCon = new SQLiteConnection(dbPath);             newCon.Open();             SQLiteCommand mCommand = new SQLiteCommand("Select Task From Tasks", newCon);             SQLiteDataReader mReader = mCommand.ExecuteReader();             while (mReader.Read())             {                 TaskQueue.Enqueue(new QueuedTask(Convert.ToString(mReader[0]), true));             }             mReader.Close();             mCommand.Dispose();             newCon.Close();         }         catch (Exception ex)         {             LiteLog myerror = new LiteLog("PersistentQueue.cs", "loadTasks", ex.Message);             myerror.UpdateClass();         }     } }

Thursday, August 04, 2016

Not a Porter Stemmer

If you are considering indexing a sequence of text documents with a view to applying some sort of search facility then there is a lot to be said for stemming.

Stemming is the process of reducing words to their stem or root such that related words are reduced to the same stem. The result may not be the true etymological stem – all stemming algorithms look for is consistency. The benefit is that searches based upon such stems can find references for related words and this reduces the pressure on search users to guess the optimal words or phrases for a given requirement.

Some examples might be:

caresses => caress
ponies => poni
cats => cat
matting => mat
meetings => meet

Perhaps the best known stemming algorithm is the “Porter Stemmer” by Martin Porter. This algorithm was “frozen” in the past and now new developments in such techniques are based upon the newer Snowball stemmer.

C# Snowball* stemmers for a range of human languages can be found on Codeplex with the warning that the code has been ported from Java (a brief review more or less confirmed that). Martin Porter’s stemming site presents his algorithm in a range of languages but again it is clear from the structure that the C# versions are translated.

I took one of the C# Porter Stemmer versions and ran it against the 23,531 word test set and reassured by a perfect score decided to then “translate” the code into something just a little easier to follow and possibly tweak. Now my version (presented below) is not a Porter Stemmer as the output varies from the standard for a handful of words (I just have to mess with things).

I preferred:
advertise, advertised and advertising being stemmed to advert rather than advertis
merchandise becomes merchand and not merchandis
etc.

You probably get the picture – I have treated “ise” the same as “ize” (which works better in GB English I think) but it would not take anyone 5 minutes to undo if you wanted to be a purist but alternately you might fancy tweaking things further.

The code presented here is almost certainly much slower than the original but it is way easier to follow and debug. I have also added a cache so that words previously stemmed can be retrieved  without re-processing although I will have to watch cache growth if the process is long running.

/// <summary> /// AditStemmer is a stemmer algorithm based upon the Porter stemmer algorithm /// but has some slight but deliberate differences in output /// see http://tartarus.org/~martin/PorterStemmer/ for more details and test data /// This software is completely free for any purpose but is not warrented to be defect free /// </summary> class AditStemmer {     private Dictionary<string, string> cache = new Dictionary<string, string>(); // could initialise this to avoid known issues (eg universal, university, universe)     public string stemWord(string word)     {         string stemWord;         if (!cache.TryGetValue(word, out stemWord))         {             stemWord = tryStem(word);             cache.Add(word, stemWord);         }         return stemWord;     }     private string tryStem(string word)     {         if (word.Length > 2)         {                 // following the steps of giants - step 1 remove plurals, -ed, -ing                 word = step1(word);                 word = step2(word);                 word = step3(word);                 word = step4(word);                 word = step5(word);                 word = step6(word);         }         return word;     }     private string step1(string word)     {         int wordLen = word.Length;         if (word.EndsWith("s"))         {             if (word.EndsWith("sses") || word.EndsWith("ies"))             {                 wordLen -= 2;             }             else if (!word.EndsWith("ss"))             {                 wordLen--;             }             word = word.Substring(0, wordLen);         }         if (word.EndsWith("eed"))         {             if (countConsonants(word, wordLen - 1) > 0)             {                 wordLen--;             }         }         else         {             if ((word.EndsWith("ed") && hasVowelInStem(word, wordLen - 2)) || (word.EndsWith("ing") && hasVowelInStem(word, wordLen - 3)))             {                 int checkTo = wordLen - (word.EndsWith("ed") ? 2 : 3);                 string stem = word.Substring(0, checkTo);                 if (stem.EndsWith("at") || stem.EndsWith("bl") || stem.EndsWith("iz") || stem.EndsWith("is"))                 {                     word = stem + "e";                 }                 else if (isDoubleCon(word, checkTo - 1))                 {                     if (!(stem.EndsWith("l") || stem.EndsWith("s") || stem.EndsWith("z")))                     {                         word = stem.Substring(0, checkTo - 1);                     }                     else { word = stem; }                 }                 else if (countConsonants(word, checkTo) == 1 && isConVowCon(word, checkTo - 1))                 {                     word = stem + "e";                 }                 else                 {                     word = stem;                 }                 wordLen = word.Length;             }         }         return word.Substring(0, wordLen);     }     private string step2(string word)     {         // not too exciting just changes y to i if another vowel in the stem - but that comes in useful later         if (word.EndsWith("y") && hasVowelInStem(word, word.Length - 1))         {             word = word.Substring(0, word.Length - 1) + "i";         }         return word;     }     private string step3(string word)     {         if (word.EndsWith("ational")) { return conditionalReplace(word, "ational", "ate"); }         if (word.EndsWith("tional")) { return conditionalReplace(word, "tional", "tion"); }         if (word.EndsWith("enci")) { return conditionalReplace(word, "enci", "ence"); }         if (word.EndsWith("anci")) { return conditionalReplace(word, "anci", "ance"); }         if (word.EndsWith("izer")) { return conditionalReplace(word, "izer", "ize"); }         if (word.EndsWith("bli")) { return conditionalReplace(word, "bli", "ble"); }         if (word.EndsWith("alli")) { return conditionalReplace(word, "alli", "al"); }         if (word.EndsWith("entli")) { return conditionalReplace(word, "entli", "ent"); }         if (word.EndsWith("eli")) { return conditionalReplace(word, "eli", "e"); }         if (word.EndsWith("ousli")) { return conditionalReplace(word, "ousli", "ous"); }         if (word.EndsWith("ization")) { return conditionalReplace(word, "ization", "ize"); }         if (word.EndsWith("isation")) { return conditionalReplace(word, "isation", "ize"); }         if (word.EndsWith("ation")) { return conditionalReplace(word, "ation", "ate"); }         if (word.EndsWith("ator")) { return conditionalReplace(word, "ator", "ate"); }         if (word.EndsWith("alism")) { return conditionalReplace(word, "alism", "al"); }         if (word.EndsWith("iveness")) { return conditionalReplace(word, "iveness", "ive"); }         if (word.EndsWith("fulness")) { return conditionalReplace(word, "fulness", "ful"); }         if (word.EndsWith("ousness")) { return conditionalReplace(word, "ousness", "ous"); }         if (word.EndsWith("aliti")) { return conditionalReplace(word, "aliti", "al"); }         if (word.EndsWith("iviti")) { return conditionalReplace(word, "iviti", "ive"); }         if (word.EndsWith("biliti")) { return conditionalReplace(word, "biliti", "ble"); }         if (word.EndsWith("logi")) { return conditionalReplace(word, "logi", "log"); }         return word;     }     private string step4(string word)     {         if (word.EndsWith("icate")) { return conditionalReplace(word, "icate", "ic"); }         if (word.EndsWith("ative")) { return conditionalReplace(word, "ative", ""); }         if (word.EndsWith("alize")) { return conditionalReplace(word, "alize", "al"); }         if (word.EndsWith("alise")) { return conditionalReplace(word, "alise", "al"); }         if (word.EndsWith("iciti")) { return conditionalReplace(word, "iciti", "ic"); }         if (word.EndsWith("ical")) { return conditionalReplace(word, "ical", "ic"); }         if (word.EndsWith("ful")) { return conditionalReplace(word, "ful", ""); }         if (word.EndsWith("ness")) { return conditionalReplace(word, "ness", ""); }         return word;     }     private string step5(string word)     {         int truncAt = word.Length;         if (word.EndsWith("al")) { truncAt -= 2; }         if (word.EndsWith("ance") || word.EndsWith("ence")) { truncAt -= 4; }         if (word.EndsWith("er")) { truncAt -= 2; }         if (word.EndsWith("ic")) { truncAt -= 2; }         if (word.EndsWith("able") || word.EndsWith("ible")) { truncAt -= 4; }         if (word.EndsWith("ant")) { truncAt -= 3; }         if (word.EndsWith("ement"))         {             truncAt -= 5;         }         else if (word.EndsWith("ment"))         {             truncAt -= 4;         }         else if (word.EndsWith("ent"))         {             truncAt -= 3;         }         if (word.EndsWith("ion") && word.Length > 4)         {             if (word.ElementAt(word.Length - 4) == 't' || word.ElementAt(word.Length - 4) == 's')             {                 truncAt -= 3;             }         }         if (word.EndsWith("ou")) { truncAt -= 2; }         if (word.EndsWith("ism")) { truncAt -= 3; }         if (word.EndsWith("ate")) { truncAt -= 3; }         if (word.EndsWith("iti")) { truncAt -= 3; }         if (word.EndsWith("ous")) { truncAt -= 3; }         if (word.EndsWith("ive")) { truncAt -= 3; }         if (word.EndsWith("ize")) { truncAt -= 3; }         if (word.EndsWith("ise")) { truncAt -= 3; }         if (countConsonants(word, truncAt) > 1)         {             word = word.Substring(0, truncAt);         }         return word;     }     private string step6(string word)     {         // mostly removes trailing e         if (word.EndsWith("e"))         {             int cCount = countConsonants(word, word.Length - 1);             if (cCount > 1 || cCount == 1 && !isConVowCon(word, word.Length - 2))             {                 word = word.Substring(0, word.Length - 1);             }         }         // reduces ll to l where appropriate         if (word.EndsWith("ll") && countConsonants(word, word.Length - 1) > 1)         {             word = word.Substring(0, word.Length - 1);         }         return word;     }     private string conditionalReplace(string word, string replace, string with)     {         // the condition is that there is at least one preceeding consonant sequence         int stemLength = word.Length - replace.Length;         if (countConsonants(word, stemLength) > 0)         {             word = word.Substring(0, stemLength) + with;         }         return word;     }     private int countConsonants(string word, int to)     {         //counts sequences of consonants before to but after any initial consonant sequence         int cCount = 0;         bool wasVowel = false;         for (var ci = 0; ci < to; ci++)         {             if (hasConsonantAt(word, ci))             {                 if (wasVowel)                 {                     wasVowel = false;                     cCount++;                 }             }             else             {                 wasVowel = true;             }         }         return cCount;     }     private bool isDoubleCon(string word, int at)     {         // is double consonant         if (at < 1) { return false; }         if (word.ElementAt(at) != word.ElementAt(at - 1))         {             return false;         }         return hasConsonantAt(word, at);     }     private bool isConVowCon(string word, int to)     {         /* is true <=> i-2,i-1,i has the form consonant - vowel - consonant      and also if the second c is not w,x or y. this is used when trying to      restore an e at the end of a short word. e.g.          cav(e), lov(e), hop(e), crim(e), but          snow, box, tray.         */         if (to < 2 || !hasConsonantAt(word, to) || hasConsonantAt(word, to - 1) || !hasConsonantAt(word, to - 2))         {             return false;         }         if (word.ElementAt(to) == 'w' || word.ElementAt(to) == 'x' || word.ElementAt(to) == 'y')         {             return false;         }         return true;     }     private bool hasVowelInStem(string word, int to)     {         for (var vi = 0; vi < to; vi++)         {             if (!hasConsonantAt(word, vi))             {                 return true;             }         }         return false;     }     private bool hasConsonantAt(string word, int at)     {         switch (word.ElementAt(at))         {             case 'a':             case 'e':             case 'i':             case 'o':             case 'u':                 return false;             case 'y':                 return (at == 0) ? true : !hasConsonantAt(word, at - 1); // y can act as consonant and vowel             default:                 return true;         }     } }

*More details of the Snowball algorithm can be found here http://snowball.tartarus.org/

<Edit>Fixed the colour errors in the demo code. The cause (something to do with text size) is under investigation</Edit>

Thursday, July 21, 2016

.NET Reflection bug

Had a fun bug with an app the other day. I had been leaving the app to run for a long time so I could monitor memory usage to check for any possible leaks. But the following bug was reported after a couple of hours:

“target invocation exception” in System.Reflection

 at the following line

this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));

but that line was in the form.designer.cs generated by Visual Studio and you would assume that this code was only executed when the form was first rendered.

I was potentially fighting an abstraction then – clearly one that had leaked

Then I remembered that the program updated the task bar icon to alert its user to potentially new and interesting occurrences. I had made sense at the time of writing to make use of the icon stored as a resource associated with the form. The image was retrieved using:

Bitmap b = this.Icon.ToBitmap();

then decorated and set as a revised taskbar icon – which is (of course) also the current form icon which just might give some insight into what was going wrong or at least a faint hint.

The icon file itself was properly formatted and contained 16x16 and 32x32 pixel bitmaps but it was clear that somewhere along the line and after several successful invocations in each instance something went wrong in mscorlib and the debug facility in Visual Studio pinned the problem back on the partial (designer) class for the form.

A couple of solutions came to mind but I went with embedding a 32x32 .png image as an additional assembly resource and retrieving that when it was required. So far, that has yet to fail but yes, there is a small memory leak somewhere so I am not finished yet. Testing and modifying programs designed to run continuously for extended periods within a Windows environment can be a challenge. 

Thursday, May 12, 2016

Drag and Drop

Drag and Drop is one of the fundamental building blocks of a modern GUI. It is so fundamental it is easy to overlook the potential for enhancement. There are lots of code fragments about if you look for enhancing the basics but most explanations seem a bit partial – hence this tutorial that I hope covers the wider ground.

Starting with the basic default process and a simple (but perhaps unlikely) example.

Consider a Label with some text that you might want to drag into a text box to edit. The TextBox has the AllowDrop attribute set to true. The drag process starts with a mouse down event on the label so we need some code to start the DragDrop process.

private void label1_MouseDown(object sender, MouseEventArgs e) {     DoDragDrop(label1.Text, DragDropEffects.Copy); }

The DoDragDrop method has been passed the label Text property as the data and the DragDropEffects value of Copy set to indicate the intention of the process. The main values are Copy, Link and Move with additional values of Scroll, None and All (multiple values can be combined with the Or operator) . The data can be supplied as an object and in this instance is a string.

Our target Textbox needs to handle two events, DragEnter and DragDrop.

private void textBox1_DragEnter(object sender, DragEventArgs e) {     if (e.Data.GetDataPresent(DataFormats.Text))     {         e.Effect = DragDropEffects.Copy;     } } private void textBox1_DragDrop(object sender, DragEventArgs e) {     textBox1.Text += e.Data.GetData(DataFormats.Text); }

That’s all that is required for this basic example. The drag drop process gets the default visual feedback effects and a drop onto the TextBox copies and adds the Label Text to the TextBox Text.

Now let us consider a more complex scenario. I might have multiple objects that could be dragged and dropped on the same page. It could also be true that things might be dragged with multiple potential purposes – I might be implicitly copying the content if I dropped it at one location but simply moving the content about within another screen area. The user needs feedback on the impact of a drop event at different locations – this makes a case for custom cursors. It is also important that the control receiving the “drop” knows the drag source as this might influence that drop action and certainly identifies the unique “content”.

As the DoDragDrop method accepts type “object” as the data source we could rewrite the code from above to use the Label (or any other) control itself:

private void label1_MouseDown(object sender, MouseEventArgs e) {     DoDragDrop(label1, DragDropEffects.Copy); } private void textBox1_DragEnter(object sender, DragEventArgs e) {     if (e.Data.GetDataPresent(label1.GetType()))     {         e.Effect = DragDropEffects.Copy;     } } private void textBox1_DragDrop(object sender, DragEventArgs e) {     textBox1.Text += ((dynamic)e.Data.GetData(label1.GetType())).Text; }

but this approach is somewhat limited as it would mean writing specific code for each individual data source.

The DataFormats class has a long list of predefined fields ranging from Bitmap through Text (as we have seen) and includes useful types like XAML. This list of DataFormats is the same list as is used by the Windows Copy/Paste functionality and it is perfectly straightforward to add additional types to that list.

The code below declares a new custom DataFormat and then uses that DataFormat identity to store an instance of a class in a DataObject and then passes that DataObject through the Drag and Drop process to the target control. The code can thus support multiple instances of said class generically.

private DataFormats.Format cardFormat = DataFormats.GetFormat("CCard"); private void label1_MouseDown(object sender, MouseEventArgs e) {     CCard mCard = new CCard(); // create a class instance     mCard.CardTitle = "New Card Title"; // set a property value     DataObject mDataObject = new DataObject(cardFormat.Name, mCard);     DoDragDrop(mDataObject, DragDropEffects.Copy); } private void textBox1_DragEnter(object sender, DragEventArgs e) {     if (e.Data.GetDataPresent(cardFormat.Name))     {         e.Effect = DragDropEffects.Copy;     } } private void textBox1_DragDrop(object sender, DragEventArgs e) {     CCard cCard = (CCard)e.Data.GetData(cardFormat.Name); // retrieve the class instance     textBox1.Text += cCard.CardTitle; // and use one or more proprty values }

In our more complex scenario, each data source type might be represented by a specific class and then each data source instance would simply supply an instance of the relevant class. The DragDrop targets can inspect the DataFormat to decide upon the relevant Effect for that DataFormat and the Drop event can process the given class instance where appropriate.

As that may not be the clearest explanation it would probably help to have some demo code. First though I want to look at custom Cursors.

My custom Cursor creation functions are encapsulated within a utility class

namespace Adit.Classes {     public struct IconInfo     {         public bool fIcon;         public int xHotspot;         public int yHotspot;         public IntPtr hbmMask;         public IntPtr hbmColor;     }     public static class CursorUtil     {         [DllImport("user32.dll")]         public static extern IntPtr CreateIconIndirect(ref IconInfo icon);         [DllImport("user32.dll")]         [return: MarshalAs(UnmanagedType.Bool)]         public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);         [DllImport("gdi32.dll")]         public static extern bool DeleteObject(IntPtr handle);         [DllImport("user32.dll", CharSet = CharSet.Auto)]         extern static bool DestroyIcon(IntPtr handle);         public static Cursor CreateCursor(Bitmap bm, int xHotspot, int yHotspot, bool resize = true)         {             IntPtr ptr = (resize) ? ((Bitmap)ResizeBitmap(bm, 32, 32)).GetHicon() : bm.GetHicon();             IconInfo inf = new IconInfo();             GetIconInfo(ptr, ref inf);             inf.xHotspot = xHotspot;             inf.yHotspot = yHotspot;             inf.fIcon = false;             IntPtr cursorPtr = CreateIconIndirect(ref inf);             if (inf.hbmColor != IntPtr.Zero) { DeleteObject(inf.hbmColor); }             if (inf.hbmMask != IntPtr.Zero) { DeleteObject(inf.hbmMask); }             if (ptr != IntPtr.Zero) { DestroyIcon(ptr); }             Cursor c = new Cursor(cursorPtr);             c.Tag = (resize) ? new Size(32, 32) : bm.Size;             return c;         }         public static Bitmap ResizeBitmap(Image image, int maxWidth, int maxHeight)         {             double ratio = System.Math.Min((double)maxHeight / image.Height, (double)maxWidth / image.Width);             var propWidth = (int)(image.Width * ratio);             var propHeight = (int)(image.Height * ratio);             var newImage = new Bitmap(propWidth, propHeight);             using (var g = Graphics.FromImage(newImage))             {                 g.DrawImage(image, 0, 0, propWidth, propHeight);             }             return newImage;         }         public static Bitmap GetControlBitmap(Control c, Color transparent)         {             var bm = new Bitmap(c.Width, c.Height);             c.DrawToBitmap(bm, new Rectangle(0, 0, c.Width, c.Height));             if (transparent != null)             {                 bm.MakeTransparent(transparent);             }             return bm;         }         public static Bitmap OverlayBitmap(Bitmap baseBitmap, Bitmap overlay, Point atPosition)         {             using (var g = Graphics.FromImage(baseBitmap))             {                 g.DrawImage(overlay, new Rectangle(atPosition, overlay.Size));             }             return baseBitmap;         }     } }

I have used these functions to create cursors from 96x96 pixel .png files (some originally sourced from the Material Icons download page). I have also used the GetControlBitmap function to create a Cursor from a Bitmap displaying a copy of a control captured at run-time.

private Cursor[] cursors = new Cursor[4];         //later cursors[0] = CursorUtil.CreateCursor((Bitmap)imageList1.Images[2], 0, 0);         //and cursors[2] = CursorUtil.CreateCursor(CursorUtil.GetControlBitmap(label1, SystemColors.Control), 0, 0, false);

Important thing is, we can easily create custom Cursors from bitmaps ready to apply during our Drag and Drop.

Just one quick aside though, the cursor "hot-spot" does not have to be at position 0,0 as in the above examples. Think of (say) the Paint.NET flood-fill or Color-Picker cursors where the hot-spot is marked by a cross or is at the tip of the "eye dropper". You might want to use something like my OverlayBitmap() function to add a suitable symbol to a control image and set the hot-spot to the relevant position in the final image.

OK - one other aside. My cursor building function adds the image size to the Tag property. You might wonder why. If you create a custom cursor (say) from a control image you might be surprised to find that the resulting cursor Size attribute will always return 32,32 (or just possibly 64,64) and not the actual size of the cursor. You can access (say) the true Width of a cursor via the Tag property with code something like:

int cWidth = ((dynamic)myCursor.Tag).Width;

We can apply our custom cursors in a GiveFeedback event handler. so first we might create custom Cursors for Copy and None and then apply them in a function

private Cursor myCopyCursor, myNoneCursor;     myCopyCursor = CursorUtil.CreateCursor((Bitmap)imageList1.Images[2], 0, 0);     myNoneCursor = CursorUtil.CreateCursor((Bitmap)imageList1.Images[3], 0, 0); private void giveFeedback(object sender, GiveFeedbackEventArgs e) {     if(e.Effect == DragDropEffects.Copy)     {         e.UseDefaultCursors = false;         Cursor.Current = myCopyCursor;     }     else if(e.Effect == DragDropEffects.None)     {         e.UseDefaultCursors = false;         Cursor.Current = myNoneCursor;     } }
We add the giveFeedback() function to the GiveFeedback event of the Form - perhaps during the form Load()

this.GiveFeedback += giveFeedback;

and then slightly counter-intuitively set the AllowDrop property to true for the form and all controls over which you anticipate the user might drag. This does not allow a drop to occur at these sites (that requires code to handle DragEnter and DragDrop but it does allow any relevant custom Cursor to be displayed during any drag over the form and control surfaces.

A point worth noting is that DragDrop event arguments include X/Y coordinates and these are screen co-ordinates as drags are not restricted to the originating window. Drag/Drop is a key element of OLE (Object Linking and Embedding). This can make using those conveniently presented values slightly tricky when managing things like local scrolling under program control. Which brings me nicely to my demo application (zipped as a Visual Studio 2015 project ready to download) that manages just that.

First off, this is demo and not production code and thus likely to include bugs, ignore edge cases, certainly takes shortcuts, is unduly verbose in places and might totally fail to explain some key point you are interested in. However it might provide a platform that can be used to try out alternate scenarios and approaches without having to build the whole thing from scratch.


Here you can see a screen image from the demo program. Addresses can be entered in the mini form on the left of the window and dragged onto the FlowLayoutPanel on the right that is acting as an "address store". To save energy, the "Fill" button will populate the form with up to 50 addresses taken randomly from an embedded list.

Addresses in the right hand panel can be dragged back to the address entry form for editing. Addresses can also be dragged up and down the list to reorder them.

We thus have two sorts of object that can be dragged for (is it) three different purposes. Some simple custom icons are created and used during the drag events.

The demo features loading data from an embedded resource (.csv file) using the Visual Basic TextFieldParser class (don't panic this is C# demo code). There are two custom DataFormats declared and used in the Drag operations. The addresses in the notional "address store" are encapsulated within multiple UserControls and these controls use Delegates to communicate with the Form. Dragging an address within the FlowLayoutPanel will trigger an automatic scroll when the scroll bar is active and the drag nears the top or bottom of the panel..

Exercises for further study might include using an image of an AddressControl as a cursor and/or dragging new addresses to a specific location in the FlowLayoutPanel instead of just adding them at the bottom irrespective of the drop location.

An analysis of the code will note that the demo "cheats" when determining the target location of a move within the FlowLayoutPanel and that it is possible to dodge around one or more address and expose the bug.

Inspect or download the demo code at https://github.com/MikeGWem/DragDropDemo

If anyone knows or finds out what DragDropEffects.Scroll actually does then please let me know as so far the answer to that has eluded me.

Addendum:

The "bug fix" (to decide where a drop happens relative to the existing list of addresses in the "store" could be based upon a calculation performed at the same time as the test to see if the panel should be scrolled.

private void maybeScrollpanel(int dragY) {     if ((dragY - panelTop) <= flowLayoutPanel1.VerticalScroll.Value && flowLayoutPanel1.VerticalScroll.Value > 0)     {         flowLayoutPanel1.VerticalScroll.Value -= (flowLayoutPanel1.VerticalScroll.Value > scrollAt) ? scrollAt : flowLayoutPanel1.VerticalScroll.Value;     }     else if (dragY - panelTop >= flowLayoutPanel1.Height - scrollAt && flowLayoutPanel1.VerticalScroll.Maximum > 0)     {         flowLayoutPanel1.VerticalScroll.Value += (flowLayoutPanel1.VerticalScroll.Maximum - flowLayoutPanel1.VerticalScroll.Value > scrollAt) ? scrollAt : flowLayoutPanel1.VerticalScroll.Maximum - flowLayoutPanel1.VerticalScroll.Value;     }     calculatedPosition = calculatePosition(dragY - panelTop); } private int calculatePosition(int dragY) {     if (flowLayoutPanel1.Controls.Count == 1 || dragY < flowLayoutPanel1.Controls[1].Height) // || allows lazy evaluation     {          return 1;     }     Point pt = new Point(flowLayoutPanel1.Width / 2, dragY); // asumes single column of controls in panel     Control cAt = flowLayoutPanel1.GetChildAtPoint(pt);     while (cAt == null && pt.Y < (flowLayoutPanel1.Height + flowLayoutPanel1.VerticalScroll.Maximum))     {         pt.Y += 5; // arbitrary - adjust at will         cAt = flowLayoutPanel1.GetChildAtPoint(pt);     }     return (cAt != null) ? ((AddressControl)cAt).Sequence : (flowLayoutPanel1.Controls.OfType<AddressControl>().Max(a => a.Sequence) + 1); }
Then applied to the drop events like

private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e) {     // the two paths could share more of the code but...     if (e.Data.GetDataPresent(dragAddress.Name))     {         //handles drop of new address onto the panel         AddressControl newControl = new AddressControl((DragAddress)e.Data.GetData(dragAddress.Name), DoControlDrag, DraggedPast, flowLayoutPanel1.Controls.Count);         foreach(AddressControl ac in flowLayoutPanel1.Controls.OfType<AddressControl>().Where(a => a.Sequence >= calculatedPosition))         {             ac.Sequence++;         }         newControl.Sequence = calculatedPosition;         flowLayoutPanel1.Controls.Add(newControl);         reorderAddresses();         resetPnlEdit();     } else if (e.Data.GetDataPresent(addressControl.Name))     {         // handles drop of an existing AddressControl into (presumably) a new position         int newSeq = 1;         foreach (AddressControl ac in flowLayoutPanel1.Controls.OfType<AddressControl>().OrderBy(a => a.Sequence))         {             if(ac.Sequence == dragItemSequence)             {                 ac.Sequence = calculatedPosition;             } else if(ac.Sequence == calculatedPosition)             {                 ac.Sequence++;                 newSeq = ac.Sequence;             } else             {                 ac.Sequence = newSeq;             }             newSeq++;         }         newSeq = 1;         // is redoing the sequence to avoid breaks a bit too anal?         foreach (AddressControl ac in flowLayoutPanel1.Controls.OfType<AddressControl>().OrderBy(a => a.Sequence))         {             ac.Sequence = newSeq;             newSeq++;         }         reorderAddresses();     } }
For the rest of the code check out GitHub at https://github.com/MikeGWem/DragDropDemo