Showing posts with label WinForms. Show all posts
Showing posts with label WinForms. Show all posts

Monday, June 15, 2009

INotifyPropertyChanged - How to and when to?

Source Code: http://www.box.net/shared/nx8uj1rm1b

Databinding has always been fascinating to me; at first it really all looks like magic, how controls just track changes, and keep everything in sync. So much so, that very often I try to use it as little as possible, because I don't like using things I don't fully understand how they work. However, it does often save you from a lot of extra code, so I think it's very useful to explore it a bit, in particular the INotifyPropertyChanged interface.

Let's first start with a quick demo. Say you have a class, let's call is MyClass:



public class MyClass
{
public int MyProperty { get; set; }
}


Now let's say we have a Windows Form that has a DataGridView that we're using to bind to a BindingList of this type:



BindingList<MyClass> bindingList = new BindingList<MyClass>();
private void Bind()
{
bindingList.Add(new MyClass { MyProperty = 10 });
bindingList.Add(new MyClass { MyProperty = 20 });
bindingList.Add(new MyClass { MyProperty = 30 });
bindingList.Add(new MyClass { MyProperty = 40 });
this.dataGridView1.DataSource = bindingList;
}


This will work, and you'll have a DataGridView with one column called MyProperty with the rows 10,20,30,40. Very nice. Suppose however, that somewhere in the application, the contents of this list change (new values from database, or user input updated data, whatever..). You'll notice, that if your code applies changes directly to the BindingList, it will NOT reflect in the DataGridView:



public void UpdateList()
{
foreach (MyClass mc in this.bindingList)
{
mc.MyProperty = 100;
}
}


Just some simple code that changes all the MyProperty's to 100. You'll notice that these changes will NOT be reflected in the DataGridView. Well, the question becomes, how then do we have the changes reflected in the DataGridView? You COULD set the DataGridView's datasource to null and then rebind, but that's hacky and clumsy.

Enter the INotifyPropertyChanged interface. The interface has one property on it, and it's actually an event:



public interface INotifyPropertyChanged
{
// Summary:
// Occurs when a property value changes.
event PropertyChangedEventHandler PropertyChanged;
}


Looks quite simple, let's implement this now properly with a Person class. I've attached the source code to this post so you can download it and see for yourself. Here's the entire Person class:



public class Person : INotifyPropertyChanged
{

private string firstName;
private string lastName;
private int age;

public Person(string firstName, string lastName, int age)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

public Person()
{

}

protected virtual void OnPropretyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}


public string FirstName
{
get
{
return this.firstName;
}
set
{
if (this.firstName != value)
{
this.firstName = value;
this.OnPropretyChanged("FirstName");
}
}
}

public string LastName
{
get
{
return this.lastName;
}
set
{
if (this.lastName != value)
{
this.lastName = value;
this.OnPropretyChanged("LastName");
}
}
}

public int Age
{
get
{
return this.age;
}
set
{
if (this.age != value)
{
this.age = value;
this.OnPropretyChanged("Age");
}
}
}

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

#endregion
}


The first thing you'll notice is that I implemented INotifyPropertyChanged. Then, in each setter of the properties, I call a method called "OnPRopertyChanged" which actually raises the event. The only downside of this, is that you can't use auto properties because the setters need to have logic. Also, since we'll be raising an event every time the value changes, it's worth putting in the extra if check:


if (this.firstName != value)
{
this.firstName = value;
this.OnPropretyChanged("FirstName");
}


just to make sure that the value actually changed.

I then created a simple Form that has a DataGridView that's bound to a BindingList<Person>. It also has a button, that when clicked, adds 5 years to everyones age.



public partial class Form1 : Form
{

private BindingList<Person> people;

public Form1()
{
InitializeComponent();
this.people = new BindingList<Person>();
this.PopulatePeople();
this.dataGridView1.DataSource = people;
}

private void PopulatePeople()
{
this.people.Add(new Person("Alex", "Friedman", 27));
this.people.Add(new Person("Jack", "Bauer", 45));
this.people.Add(new Person("Tony", "Almeda", 39));
this.people.Add(new Person("Chloe", "O'Brien", 37));
this.people.Add(new Person("Bill", "Buchanan", 50));
}

private void ChangeAges()
{
foreach (Person p in this.people)
{
p.Age += 5;
}
}

private void buttonChange_Click(object sender, EventArgs e)
{
this.ChangeAges();
}
}


If you run this, you'll notice that DataGridView does in fact get updated, even though I just manipulated the underlying list. I never actually touch the DataGridView itself. Internally, the BindingList checks to see if the class of type T (remember, BindingList is generic) implements INotfiyPropertyChanged. If it does, it hooks into that event, and when the event is raised, it updates itself.

The one thing to be careful is that when raising this event, you actually hardcode the name of the property. Personally, I wish there was a better way, but anything I've seen online so far had some serious overhead (like using the StackFrame to figure out which property is changing) so if someone knows of a better way, please let me know!

UPDATE: I recently blogged about a better approach to dealing with the INotifyPropertyChanged event that takes care of the hardcoded strings issue. Check it out here.

Thursday, June 11, 2009

Tweaking a DataGridView ComboBoxColumn to allow editing.

Let me first say this. I love the DataGridView and I think it's an awesome and very versatile control. It's highly customizable, and you can create a damn near excel-like application around it. Having said that though, anyone who's ever used a DataGridView knows just how "quirky" the stupid thing can be. Things that look like they should be simple, require clever hacks to implement.

Recently, I was creating an in-house app here at work, in which I needed a DataGridView with a ComboBoxColumn but with one wrinkle. Out of the box, the ComboBoxColumn doesn't support editing. Meaning, it acts like a ComboBox that has the DropDownStyle set to DropDownList (which doesn't allow the user to enter new values). We, however, did need to have the ability for the user to enter new values. I thought that this would be a simple property that I'd be able to set on the ComboBoxColumn. Yea, well there is no such property, so queue clever hack!

The first thing you need to do is hook into the DataGridView's EditingControlShowing event. This event fires when the actual ComboBox is "dropped down". The interesting thing is that the EventArgs has a property on it "Control" that can be cast to a standard WinForms ComboBox.

To demonstrate, I've created a simple Windows App that has a button on it and a DataGridView. It also has a method that just returns a list of strings that contains all the days of the week (needed something just for testing). When the button is clicked, the DataGridView gets populated with a ComboBoxColumn with all the days. Here's the entire code:



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace DataGridViewComboBoxTesting
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.dataGridView1.EditingControlShowing += HandleEditShowing;
}

private void HandleEditShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
var cbo = e.Control as ComboBox;
if (cbo == null)
{
return;
}

cbo.DropDownStyle = ComboBoxStyle.DropDown;
cbo.Validating -= HandleComboBoxValidating;
cbo.Validating += HandleComboBoxValidating;
}

private void HandleComboBoxValidating(object sender, CancelEventArgs e)
{
var combo = sender as DataGridViewComboBoxEditingControl;
if (combo == null)
{
return;
}
if (!combo.Items.Contains(combo.Text)) //check if item is already in drop down, if not, add it to all
{
var comboColumn = this.dataGridView1.Columns[this.dataGridView1.CurrentCell.ColumnIndex]
as DataGridViewComboBoxColumn;
combo.Items.Add(combo.Text);
comboColumn.Items.Add(combo.Text);
this.dataGridView1.CurrentCell.Value = combo.Text;
}
}

private void button1_Click(object sender, EventArgs e)
{
var cboColumn = new DataGridViewComboBoxColumn
{
Name = "ComboBox",
HeaderText = "Combo Box Column"
};

foreach (var day in this.GetListOfStrings())
{
cboColumn.Items.Add(day);
}

this.dataGridView1.Columns.Add(cboColumn);
}

public IEnumerable<string> GetListOfStrings()
{
foreach (var day in Enum.GetValues(typeof(DayOfWeek)))
{
yield return day.ToString();
}
}
}
}


As you can see, in the HandleEditShowing method, we can get access the the underlying ComboBox itself, and set the DropDownStyle property right there. We then do a little more hackery so that when the user enters a new value, we can add it to all other ComboBox's in that column. We do that by hooking into the ComboBox's Validating event (first we unhook it in case we're already hooked into it, so that our event handler doesn't get called more than once).


private void HandleEditShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
var cbo = e.Control as ComboBox;
if (cbo == null)
{
return;
}

cbo.DropDownStyle = ComboBoxStyle.DropDown;
cbo.Validating -= HandleComboBoxValidating;
cbo.Validating += HandleComboBoxValidating;
}


The event handler then looks like this:



private void HandleComboBoxValidating(object sender, CancelEventArgs e)
{
var combo = sender as DataGridViewComboBoxEditingControl;
if (combo == null)
{
return;
}
if (!combo.Items.Contains(combo.Text)) //check if item is already in drop down, if not, add it to all
{
var comboColumn = this.dataGridView1.Columns[this.dataGridView1.CurrentCell.ColumnIndex] as DataGridViewComboBoxColumn;
combo.Items.Add(combo.Text);
comboColumn.Items.Add(combo.Text);
this.dataGridView1.CurrentCell.Value = combo.Text;
}
}


Simple. We just check if it's already in the list, if it's not then add it to the combo box as well as add it to all other combo boxes.

Here's a link to the source code. In the code I also created a class called DataGridViewComboBoxColumnEx which basically inherits from DataGridViewComboBoxColumn and encapsulates all this hackery nicely. You just use it as you normally would but it allows editing.

Source code: http://www.box.net/shared/73bmzmv1r6

Tuesday, June 2, 2009

Creating a Splash Screen in .NET with a progress bar.

EDIT: I've added a link to the source code: http://www.box.net/shared/xbo9xvlguu

Many times an application needs to do many time consuming operations at start-up. Sometimes you need to read data from a database, sometimes you may need to retrieve some data from a web service. When this happens, it's often useful to display a "Splash Screen" to the user, with a company logo or something, along with a progress bar to indicate how much longer it will take for the app to load. While it may sound simple at first, it can be a bit tricky; if you simply show a screen, and do your time consuming operations, your UI will hang and your progress bar will never update. Therefore, there's some threading involved (not too much, don't get scared!), so I'll demonstrate a simple example here.

Start off by creating a simple Windows Forms project. Once it's loaded, add another windows form (besides for the Form1 that's already there) and call it "SplashScreen". To get the look and feel right, let's set some properties:

  • FormBorderStyle: None
  • TopMost : True
  • StartPosition: CenterScreen
Now, in the properties window, find the BackgroundImage property and click the little elipsis {...} and select a picture from your hard drive. I also then changed the BackgroundImageLayout property to None but you can do whatever you want. Then, add a progress bar to the bottom of the form, and set the Dock property to Bottom. Here's what my splash screen looks like in the designer: (not sure why I chose a Halo picture....)

Now, we need to give access to someone outside of this class to update the progress (the progressBar is a private member and can't be accessed.) Here's the problem; if we simply wrap the progress bar's Value property in our own getter/setter like this:



public int Progress
{
get
{
return this.progressBar1.Value;
}
set
{
this.progressBar1.Value = value;
}
}

while you can do that, remember, this splash screen will be shown in a seperate thread. If you then try to access this property from your main thread, you'll get an InvalidOperationException that "Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on." So, in order to be able to set any of the UI elements from another thread, we need to call the form's Invoke method which takes a delegate. Here's the complete code for the SplashScreen class:


using System.Windows.Forms;

namespace SplashScreenTesting
{
public partial class SplashScreen : Form
{
private delegate void ProgressDelegate(int progress);

private ProgressDelegate del;
public SplashScreen()
{
InitializeComponent();
this.progressBar1.Maximum = 100;
del = this.UpdateProgressInternal;
}

private void UpdateProgressInternal(int progress)
{
if (this.Handle == null)
{
return;
}

this.progressBar1.Value = progress;
}

public void UpdateProgress(int progress)
{
this.Invoke(del, progress);
}
}
}

As you can see, we created a delegate that we'll use to invoke the update to the progress bar. (The reason why I have a null check for this.Handle is because I was getting an exception right at the start up that the Handle wasn't created yet.)

Ok, now let's create a class that simulates a time consuming operation. Basically, it just calculates Math.Pow for numbers 1 - 100 raised to the 1 - 500,000th power. Every time we move on to another outer number (the 1 - 100) we raise an event that reports progress. Once it's done, we raise an event that we're done. Here's the complete class:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SplashScreenTesting
{
public class Hardworker
{
public event EventHandler<HardWorkerEventArgs> ProgressChanged;
public event EventHandler HardWorkDone;

public void DoHardWork()
{
for (int i = 1; i <= 100; i++)
{
for (int j = 1; j <= 500000; j++)
{
Math.Pow(i, j);
}
this.OnProgressChanged(i);
}

this.OnHardWorkDone();
}

private void OnProgressChanged(int progress)
{
var handler = this.ProgressChanged;
if (handler != null)
{
handler(this, new HardWorkerEventArgs(progress));
}
}

private void OnHardWorkDone()
{
var handler = this.HardWorkDone;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}

public class HardWorkerEventArgs : EventArgs
{
public HardWorkerEventArgs(int progress)
{
this.Progress = progress;
}

public int Progress
{
get;
private set;
}
}
}


I also created a custom event args so that we can pass the progress on to the subscriber of our ProgressChanged event. (As a side note, if you're wondering about the strange assignement taking place when raising the event, see Eric Lippert's blog on the subject.)

Now, on to the main part of the app; the displaying of the splash screen. In the Form1's load event, we'll spawn off another thread to actually display the splash screen and then on the main thread we'll do our "Hard Work". Once the HardWorker has reported that the progress is complete, we'll dispose of the splashscreen and display the main Form. Here's the code, I'll try my best to explain:

using System;
using System.Windows.Forms;
using System.Threading;

namespace SplashScreenTesting
{
public partial class Form1 : Form
{
private SplashScreen splashScreen;
private bool done = false;
public Form1()
{
InitializeComponent();
this.Load += new EventHandler(HandleFormLoad);
this.splashScreen = new SplashScreen();
}

private void HandleFormLoad(object sender, EventArgs e)
{
this.Hide();

Thread thread = new Thread(new ThreadStart(this.ShowSplashScreen));
thread.Start();

Hardworker worker = new Hardworker();
worker.ProgressChanged += (o, ex) =>
{
this.splashScreen.UpdateProgress(ex.Progress);
};

worker.HardWorkDone += (o, ex) =>
{
done = true;
this.Show();
};

worker.DoHardWork();
}



private void ShowSplashScreen()
{
splashScreen.Show();
while (!done)
{
Application.DoEvents();
}
splashScreen.Close();
this.splashScreen.Dispose();
}
}
}

In the constructor we just hook into the Load event and new up the SplashScreen. Then, in the Load event handler, we first hide the current form, because we don't want that to be seen just yet. We then create a Thread and pass in a delegate to our ShowSplashScreen method. The show splash screen method is what's actually going to be run on a seperate thread. First, it displays the SplashScreen. Then, it just sits there in a constant loop waiting for the "done" bool to be set to true. The key ingredient here is the call to Application.Doevents(). I think Microsoft does a good job explaining what this does so I'll let them do that talking:

When you run a Windows Form, it creates the new form, which then waits for events to handle. Each time the form handles an event, it processes all the code associated with that event. All other events wait in the queue. While your code handles the event, your application does not respond. For example, the window does not repaint if another window is dragged on top.

If you call DoEvents in your code, your application can handle the other events. For example, if you have a form that adds data to a ListBox and add DoEvents to your code, your form repaints when another window is dragged over it. If you remove DoEvents from your code, your form will not repaint until the click event handler of the button is finished executing. For more information on messaging, see User Input in Windows Forms.


Basically, it allows other events to be taken care of, even though the current procedure is blocking execution. This allows our progress bar to be updated.

Back in the form load, we then new up our HardWorker and hook into it's events. (I'm using the more terse Lambda syntax, see my previous blog post on the subject here for more information.) Basically, every time the HardWorker reports progress, we update our progress bar. Then when it's done, we set our done flag to true, and show the main form. Finally, we actually kick it off with a call to DoHardWork();

Try it out and run it. It's actually kinda neat to see it in action. This is obviously a very rough example, but it should give you a basic idea on how to get this done.

Sunday, May 24, 2009

When KeyPressed / KeyDown just isn't enough. An adventure in GetKeyboardState.

Source Code: http://www.box.net/shared/y7v5jskfd1

A question on StackOverflow this weekend, piqued my interest. It was about some guy that was creating a Tetris game in C# (using Winforms) and was having issues with keyboard input. Basically, he was hooking into the Forms KeyDown event, and if the key was either left down or right, he'd move the tetris piece accordingly. The problem he was running into was if the user would hold down more than one key at a time (which is very common when playing Tetris or any other game for that matter), only one key would register.

Having messed with XNA in the past (I still hope to release a game to the XBOX Community games in this life time :-P ), I had a hunch. In XNA, things work a little different. A little background on how games work in general. All games consist of a "Game Loop". Basically, the entire application runs in one gigantic loop. You have a timer that constantly runs in the background, and every time the timer "ticks" two things happen. Update() is called, and Draw() is called. During the update routine, you update your game logic, ie: move player's position, move enemies position etc.. Then, during the Draw routine, you draw everything to screen.

It is during this Update routine, where you "poll" the keyboard (or gamepad when working on the XBOX 360) to find out what's going on. Here's a small sample:

56 var state = Keyboard.GetState();

57 bool downPressed = state.IsKeyDown(Keys.Down);


As you can see, you "ask" the keyboard if the Down key is pressed at the current time. The cool thing about this method is that you can "ask" for multiple keys in one trip. There's nothing stopping you from doing this:

56 var state = Keyboard.GetState();

57 bool downPressed = state.IsKeyDown(Keys.Down);

58 bool upPressed = state.IsKeyDown(Keys.Up);



In Windows Forms though, if you listen for the KeyDown event, the KeyEventArgs will give you the Keycode, but only of ONE key. So, if more than one key is pressed, what do you do?

After some searching, I found that there's a "GetKeyboardState" function that's part of the Win32 API. We should be able to P/Invoke this function, and poll the keyboard for more than one key. According to the Microsoft documentation, you pass in the int value of the key, and you get back a short.

The return value specifies the status of the specified virtual key, as follows:

  • If the high-order bit is 1, the key is down; otherwise, it is up.
  • If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled if it is turned on. The key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any) on the keyboard will be on when the key is toggled, and off when the key is untoggled.
So, I found this helpful little class online that wraps this API call nicely.

First, a simple struct to hold the Key's state:



public struct KeyStateInfo
{
private Keys key;
private bool isPressed;
private bool isToggled;

public KeyStateInfo(Keys key, bool ispressed, bool istoggled)
{
this.key = key;
isPressed = ispressed;
isToggled = istoggled;
}

public static KeyStateInfo Default
{
get
{
return new KeyStateInfo(Keys.None, false, false);
}
}

public Keys Key
{
get { return key; }
}

public bool IsPressed
{
get { return isPressed; }
}

public bool IsToggled
{
get { return isToggled; }
}
}

Then, here's the actual class that wraps the P/Invoke:



using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class KeyboardInfo
{
private KeyboardInfo() { }
[DllImport("user32")]
private static extern short GetKeyState(int vKey);

public static KeyStateInfo GetKeyState(Keys key)
{
short keyState = GetKeyState((int)key);
int low = Low(keyState), high = High(keyState);
bool toggled = low == 1;
bool pressed = high == 1;
return new KeyStateInfo(key, pressed, toggled);
}
private static int High(int keyState)
{
return keyState > 0 ? keyState >> 0x10
: (keyState >> 0x10) & 0x1;
}
private static int Low(int keyState)
{
return keyState & 0xffff;
}
}


Simple enough. To prove that this works now with more than one key, I wrote a simple windows app that moves a ball around the form based on the user's pressing of the arrow keys. You'll notice, that if you press two arrows at once, it will move the ball diagonally, proving that it accepts more than one key at a time. Here's the code:

First, I created a simple Ball class:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace WindowsFormsApplication1
{
public class Ball
{
private Brush brush;

public float X { get; set; }
public float Y { get; set; }
public float DX { get; set; }
public float DY { get; set; }
public Color Color { get; set; }
public float Size { get; set; }

public void Draw(Graphics g)
{
if (this.brush == null)
{
this.brush = new SolidBrush(this.Color);
}
g.FillEllipse(this.brush, X, Y, Size, Size);
}

public void MoveRight()
{
this.X += DX;
}

public void MoveLeft()
{
this.X -= this.DX;
}

public void MoveUp()
{
this.Y -= this.DY;
}

public void MoveDown()
{
this.Y += this.DY;
}
}

}


This class basically holds the coordinates of the ball, and the "velocity" (DX, and DY), or the speed at which the ball will move each time the key is pressed. It also holds the color and size.

Then, here's the main Form code:



using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Ball ball;
private Timer timer;
public Form1()
{
InitializeComponent();
this.ball = new Ball
{
X = 10f,
Y = 10f,
DX = 2f,
DY = 2f,
Color = Color.Red,
Size = 10f
};
this.timer = new Timer();
timer.Interval = 20;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
var left = KeyboardInfo.GetKeyState(Keys.Left);
var right = KeyboardInfo.GetKeyState(Keys.Right);
var up = KeyboardInfo.GetKeyState(Keys.Up);
var down = KeyboardInfo.GetKeyState(Keys.Down);

if (left.IsPressed)
{
ball.MoveLeft();
this.Invalidate();
}

if (right.IsPressed)
{
ball.MoveRight();
this.Invalidate();
}

if (up.IsPressed)
{
ball.MoveUp();
this.Invalidate();
}

if (down.IsPressed)
{
ball.MoveDown();
this.Invalidate();
}


}


protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (this.ball != null)
{
this.ball.Draw(e.Graphics);
}
}
}


}

First, I create a red ball that's 10 pixels in size. Then, I create a Timer which will "tick" every 20 milliseconds. This is simple to simulate a game loop. It won't actually "Draw" every tick, it will only ever redraw if one of the arrow keys is pressed. The event handler for the Timer is where the meat of this lies:


void timer_Tick(object sender, EventArgs e)
{
var left = KeyboardInfo.GetKeyState(Keys.Left);
var right = KeyboardInfo.GetKeyState(Keys.Right);
var up = KeyboardInfo.GetKeyState(Keys.Up);
var down = KeyboardInfo.GetKeyState(Keys.Down);

if (left.IsPressed)
{
ball.MoveLeft();
this.Invalidate();
}

if (right.IsPressed)
{
ball.MoveRight();
this.Invalidate();
}

if (up.IsPressed)
{
ball.MoveUp();
this.Invalidate();
}

if (down.IsPressed)
{
ball.MoveDown();
this.Invalidate();
}
}

First, we "poll" the keyboard for the arrows keys. We poll for all four of them, therefore if more than one is pressed, we'll be able to react to all of them. Then, if either key is held down, we move the ball in that direction and call Invalidate() to allow the form to repaint itself.

One final thing I'd like to point out, is that with the standard KeyDown event in Windows Forms, you can get "modifiers" (Shift, Ctrl, Alt etc.) as well as the key that was pressed. So if you're writing an app where you want to have some shortcut like say CTRL + A, you don't need to do this. This is only when you want to get info on more than one standard key................Which is why, you'll probably never need this in a real app outside of games (in which case, you're better off with XNA) but it's still something that's good to know.

Sunday, March 29, 2009

WinForms Drag & Drop. A simple example to get you started.

When using computers these days, we've all gotten accustomed to dragging and dropping things from one place to another. We do it all the time, and it gives your application that extra usability factor that the end user will appreciate. At work, I was working on an app that gave the user the ability to "Screen" on various columns of data. The user needed to have the ability to choose specifically which fields they want to screen on. So, we came up with the idea of having two treeviews. On the left hand side were all the available columns in the database and on the right was an empty treeview. The user would then be able to drag and drop fields from one to the other. So, here's the basic way of implementing Drag / Drop in your windows app.

For simplicity in this example, I decided to go with a Listbox on the left and a Treeview on the right. The idea here will be simple. The user will be able to drag an item from the left, and move it to the treeview on the right. We will have to track where the user "drops" the item, so that we can place it under the proper parent node. Here's what the sample form looks like:




When the form loads, we'll click on the Populate controls button. That will add the next 10 dates to the listbox on the left. It will also add two parents nodes to the treeview as well as two subnodes to each of the parent nodes.

Here's the full code, I'll go through and explain it all as best I can:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace SetFocusDemo.DragDropSample
{
    public partial class DragDropSample : Form
    {
        public DragDropSample()
        {
            InitializeComponent();
 
            this.treeView1.AllowDrop = true;
            this.listBox1.AllowDrop = true;
 
            this.listBox1.MouseDown += new MouseEventHandler(listBox1_MouseDown);
            this.listBox1.DragOver += new DragEventHandler(listBox1_DragOver);
 
            this.treeView1.DragEnter += new DragEventHandler(treeView1_DragEnter);
            this.treeView1.DragDrop += new DragEventHandler(treeView1_DragDrop);
 
        }
 
        private void buttonPopulate_Click(object sender, EventArgs e)
        {
            this.PopulateListBox();
            this.PopulateTreeView();
        }
 
        private void PopulateListBox()
        {
            for (int i = 0; i <= 10; i++)
            {
                this.listBox1.Items.Add(DateTime.Now.AddDays(i));
            }
        }
 
        private void PopulateTreeView()
        {
            for (int i = 1; i <= 2; i++)
            {
                TreeNode node = new TreeNode("Node" + i);
                for (int j = 1; j <= 2; j++)
                {
                    node.Nodes.Add("SubNode" + j);
                }
                this.treeView1.Nodes.Add(node);
            }
        }
 
        private void listBox1_MouseDown(object sender, MouseEventArgs e)
        {
            this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
        }
 
        private void listBox1_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }
 
        private void treeView1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }
 
        private void treeView1_DragDrop(object sender, DragEventArgs e)
        {
 
            TreeNode nodeToDropIn = this.treeView1.GetNodeAt(this.treeView1.PointToClient(new Point(e.X, e.Y)));
            if (nodeToDropIn == null) { return; }
            if (nodeToDropIn.Level > 0)
            {
                nodeToDropIn = nodeToDropIn.Parent;
            }
 
            object data = e.Data.GetData(typeof(DateTime));
            if (data == null) { return; }
            nodeToDropIn.Nodes.Add(data.ToString());
            this.listBox1.Items.Remove(data);
        }
    }
}


Ok, let's start with the constructor. First thing you need to make sure, is that if you want a control to be a "Drop Target" you need to set the "AllowDrop" property to true. You'll also notice that we're setting the "AllowDrop" property of the listbox. Even though we won't actually be dropping anything in the listbox, the only way to get the correct icon while dragging out of the listbox, is to set it's AllowDrop to true as well.

            this.listBox1.MouseDown += new MouseEventHandler(listBox1_MouseDown);
            this.listBox1.DragOver += new DragEventHandler(listBox1_DragOver);
 
            this.treeView1.DragEnter += new DragEventHandler(treeView1_DragEnter);
            this.treeView1.DragDrop += new DragEventHandler(treeView1_DragDrop);


Here, we hook up to all the events that we'll need to make this happen. I'll explain it when we get to the handlers.

        private void buttonPopulate_Click(object sender, EventArgs e)
        {
            this.PopulateListBox();
            this.PopulateTreeView();
        }
 
        private void PopulateListBox()
        {
            for (int i = 0; i <= 10; i++)
            {
                this.listBox1.Items.Add(DateTime.Now.AddDays(i));
            }
        }
 
        private void PopulateTreeView()
        {
            for (int i = 1; i <= 2; i++)
            {
                TreeNode node = new TreeNode("Node" + i);
                for (int j = 1; j <= 2; j++)
                {
                    node.Nodes.Add("SubNode" + j);
                }
                this.treeView1.Nodes.Add(node);
            }
        }


This is just some demo code to set up are controls. In the PopulateListBox() method we just do a for loop from 0 to 10 and add the date for the next 10 days to the listbox. In the PopulateTreeView() method, we just put two parent nodes along with two child nodes in the treeview. Again, this is just for the purpose of this demo.

        private void listBox1_MouseDown(object sender, MouseEventArgs e)
        {
            this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
        }


Here's where it starts getting interesting. The first event we deal with, is the "MouseDown" on the listbox. This kicks off the Drag / Drop; when the user actually clicks on an item. Here, we call "DoDragDrop" on the listbox. The arguments that the DoDragDrop method expect are "object data", and "DragDropEffects allowedEffects". The data in the first argument, is the actual object that you want to move from one control to the other. In our case, we just grab the listbox selected item, which will be a DateTime. We then use the Move drag effect, which gives the nice visual mouse cursor we expect for a move.

        private void listBox1_DragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }


The next event we handle is the listbox DragOver event. Since we called the DoDragDrop in the mousedown, when the user starts to "drag" the item out, the DragOver event is now raised in the listbox. We are only dealing with this event so that we can continue to show the "move" mouse cursor.

Once the user leaves the Listbox, the DragLeave event is raised. In this case I'm not handling it because I have no need for it, but if you do need it, it does get raised.

        private void treeView1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }


The next event we're listening for is the "DragEnter" on the Treeview. At this point, the user has clicked his mouse on one of the dates, dragged it out of the listbox, and now moved his mouse into the treeview. All we're doing here, is just setting the DragEffects to move so that we can continue to show the correct mouse cursor.

        private void treeView1_DragDrop(object sender, DragEventArgs e)
        {
 
            TreeNode nodeToDropIn = this.treeView1.GetNodeAt(this.treeView1.PointToClient(new Point(e.X, e.Y)));
            if (nodeToDropIn == null) { return; }
            if (nodeToDropIn.Level > 0)
            {
                nodeToDropIn = nodeToDropIn.Parent;
            }
 
            object data = e.Data.GetData(typeof(DateTime));
            if (data == null) { return; }
            nodeToDropIn.Nodes.Add(data.ToString());
            this.listBox1.Items.Remove(data);
        }


Finally, we actually deal with the "main event" (pun intended). This is where the user finally lets go of the mouse and "Drops" the item into the TreeView. First we need to determine where the user let go of his mouse. There's a very helpful method on the TreeView called "GetNodeAt(Point pt)" which takes a Point as a parameter, and gives you back the TreeNode at that location. The Point that's passed to us through the DragEventArgs is in screen coordinates. Therefore, we first need to call "PointToClient" to get us the coordinates relative to the TreeView. Once we have that, we can get the exact node where the user let go of the mouse.

Now, we just need to determine if the node where the user dropped the item is a parent node or a child node. This is the business rule I decided to set up for this example; that the user can only drag a date into the treeview as a subnode. We will not allow them to add parent nodes. Therefore, we first need to determine if the node is a parent or a sub. Once we have the parent node, we will add the dropped item as a subnode.

            object data = e.Data.GetData(typeof(DateTime));
            if (data == null) { return; }


Here we retrieve the item that was brought over from the listbox through the eventargs. We call the GetData method to actually retrieve the item which in our case is a DateTime object.

            nodeToDropIn.Nodes.Add(data.ToString());
            this.listBox1.Items.Remove(data);


Once that's done, we just add it as a node, and remove the item from the listbox. Run the sample and try it yourself. Click the Populate Button and then drag items from the listbox to the treeview. Cool stuff no?

I admit, it's a bit of a pain to get this all to work, but the end result is worth it. Users are accustomed to drag and drop and will appreciate the extra touch in your app.