MexiCode Ideas en codigo

Drag & Drop from ListView to TreeView

Posted on March 9, 2010

I started working on a new project at work. This is the "fun stage" of the project: the prototypes and the investigation part.

One of the UI requirements was to drag and drop elements from a ListView to a TreeNode. So, this is how to enable Drag&Drop:

First, you have to enable the AllowDrop property for your TreeView

treeView1.AllowDrop = true;

Then, in your ListView, you have to add code to the ItemDrag event:

listView1.DoDragDrop(listView1.SelectedItems, DragDropEffects.Move);

The DoDragDrop method begins a Drag&Drop operation. The first parameter, is the element(s) you want to drag around. The second, is the type of dragging-and-dropping you want to do. Here is a list:

None The drop target does not accept the data.
Copy The data from the drag source is copied to the drop target.
Move The data from the drag source is moved to the drop target.
Link The data from the drag source is linked to the drop target.
Scroll The target can be scrolled while dragging to locate a drop position that is not currently visible in the target.
All The combination of the Copy, Move, and Scroll effects.

You can combine more than 1 DragDropEffect by bitwise ORing them:

listView1.DoDragDrop(listView1.SelectedItems, DragDropEffects.All | DragDropEffects.Link);

In my project, I only need DragDropEffects.Move functionality.

Well. Now we only have two more things to do. First, we have to take care of the DragOver event. This is raised by your target control (in this case a TreeView) every time the mouse moves over the target's visible area.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void treeView1_DragOver(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(ListView.SelectedListViewItemCollection)))
    {
        e.Effect = DragDropEffects.Move;
    }
 
    Point position = treeView1.PointToClient(new Point(e.X, e.Y));
    TreeNode tn = treeView1.GetNodeAt(position);
 
    if (tn != null)
    {                
        treeView1.SelectedNode = tn;                
        if (!tn.IsExpanded)
        {
            tn.Expand();
        }                
    }
}

There are some interesting bits of code in there:

if (e.Data.GetDataPresent(typeof(ListView.SelectedListViewItemCollection)))
{
    e.Effect = DragDropEffects.Move;
}

Our data (the one we are dragging around) is stored in the Data property of the event. The GetDataPresent method determines whether this data can be converted to the specified format.

In this case, our format is ListView.SelectedListViewItemCollection. Why? you may ask. Well, because this is the returning type of listView1.SelectedItems (which is the object we are dragging around, remember?).

We could put this particular piece of code inside the target's (TreeView) DragEnter event. This way, it only has to be executed once.

This is also important:

Point position = treeView1.PointToClient(new Point(e.X, e.Y));
TreeNode tn = treeView1.GetNodeAt(position);

The DragEventArgs object holds some information about the event. In this case we are using e.X and e.Y to know the location of the mouse at any given time. But the thing is that the mouse location is relative to the screen, so we have to convert it to client coordinates. That's exactly what PointToClient does.

Now that we have the position relative to the client, we can get the TreeNode under that position. If there is no TreeNode under that position (maybe the mouse is moving over whitespace) it returns null.

The rest of the code is trivial, it expands a TreeNode if its not expanded already.

Lastly, we have the DragDrop event, in which we release the mouse button over our target TreeNode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void treeView1_DragDrop(object sender, DragEventArgs e)
{
    Point position = treeView1.PointToClient(new Point(e.X, e.Y));
 
    if (e.Data.GetDataPresent(typeof(ListView.SelectedListViewItemCollection)))
    {
        TreeNode tn = treeView1.GetNodeAt(position);
        var li = (ListView.SelectedListViewItemCollection)e.Data.GetData(typeof(ListView.SelectedListViewItemCollection));
        foreach (ListViewItem item in li)
        {
            MessageBox.Show(tn.Text + " = " + item.Text);                 
        }                
    }
}

The only interesting or new line in here is this one:

var li = (ListView.SelectedListViewItemCollection)e.Data.GetData(typeof(ListView.SelectedListViewItemCollection));

this method retrieves the data associated with the specified data format (at least, that's what MSDN says) but you still have to cast it accordingly.

Once you have your data, you can do anything you want with it (i'm just displaying which TreeNode received the Items and the Items' text)

Happy hacking.

UPDATE

If you want to see which TreeNode you're selecting while dragging, just add this to your code:

1
treeView1.HideSelection = false;

Formatting DataGridView cell with custom DataSource

Posted on January 13, 2010

When you use a custom DataSource (in my case a generic list) the properties become the columns and the property names, become the column header.

I needed to format a DateTime field, so I did this:

1
2
3
4
5
6
7
8
9
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (dataGridView1.Columns[e.ColumnIndex].DataPropertyName == "Date")
    {	
        DateTime value = (DateTime)e.Value;
        e.Value = value.ToString("G");
        e.FormattingApplied = true;
    }
}

In this case, "Date" is the property name of a DateTime field.

In case you are curious, the DataSource assignment is like this:

1
2
3
List<GoodReceipts> grrs = new List<GoodReceipts>();
grrs.AddRange(logic.GetGRRData(consumptions, validSince, validTo));     
dataGridView1.DataSource = grrs;

Serializing Classes

Posted on January 4, 2010

I stumbled upon a new problem. Again.

In a project I'm currently involved in, I had to use serializable classes. The application saves the serialized object in a database, and then, another application can retrieve the object and deserialize it.

Or at least, that's what i thought.

I was doing some "cut&paste" programming, and I thought it would be easier to just have 2 copies of the serializable class in two different projects. (this one)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections.Generic;
 
namespace Trivius.Wix.ORM
{
    [Serializable]
    public class Message
    {
 
        [Serializable]
        public class ColorMessage
        {
            public string Message { get; set; }
            public byte Color { get; set; }
        }
 
        public byte Effect { get; set; }
        List<ColorMessage> colorMessages = new List<ColorMessage>();
 
        public void AddMessage(ColorMessage msg)
        {
            colorMessages.Add(msg);
        }
 
        public List<ColorMessage> GetMessages()
        {
            return colorMessages;
        }            
    }    
}

In one application, this approach worked beautifully. It saved and retrieved from the database like a charm. But when I tried to deserialize the object in another application (using the other copy of the exact same class) the compiler told me that:

Cannot cast object of type Trivius.Wix.Message into an object of type Trivius.Wix.Message

Not nice.

So, what I did, is that I separated the serializable class into a Dll, and added a reference to that same class in both projects, and it worked.

I should have done that in the first place. :(

DataGridView EditOnEnter and row deleting

Posted on December 21, 2009

When you're working in a datagridview, sometimes is useful to use the EditOnEnter edit mode.
But for some reason (i.e. Bug) the deleting functionality is broken under this scheme.

To overcome this, you could use this little hack.

You'll need 2 events:

CellEnter

        private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
        {
            DataGridView dgv = sender as DataGridView;
            if (dgv != null)
            {
                dgv.EditMode = DataGridViewEditMode.EditOnEnter;
            }
        }

MouseClick

        private void dataGridView1_MouseClick(object sender, MouseEventArgs e)
        {
            DataGridView dgv = sender as DataGridView;
            if (dgv != null)
            {
                DataGridView.HitTestInfo hti = dgv.HitTest(e.X, e.Y);
 
                if (hti.Type == DataGridViewHitTestType.RowHeader)
                {
                    dgv.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
                    dgv.EndEdit();
                }
            }
        }

Importing data directly from Excel

Posted on December 21, 2009
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;
 
namespace ExcelImport
{
    public partial class Form1 : Form
    {
 
        public Form1()
        {
            InitializeComponent();
            System.IO.FileInfo info = new System.IO.FileInfo("C:\\cosa.xls");
            this.openFileDialog1.FileName = "*.xls";
            DataTable dt = new DataTable();
            dt.Columns.Add("ID");
            dt.Columns.Add("Name");
            dt.Columns.Add("Date");
            dt.Columns.Add("Last Name");
            dt.Columns.Add("Address");
 
            if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
            {
 
                /* For some reason, all excel lists and "arrays" are not zero based.
                 * This is important to keep in mind while looping thru everything
                 */
 
                Excel.Application ExcelObj = new Excel.Application();
                //I really don't know what the other 13 or so parameters do, but i swear this works
                Excel.Workbook theWorkbook = ExcelObj.Workbooks.Open(openFileDialog1.FileName, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
                Excel.Sheets sheets = theWorkbook.Worksheets;
                Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);
                /* This is neat. Only retrieve the used range in the worksheet.
                 * Instead you could load the entire worksheet and get ranges for each row,
                 * but this approach is *much* faster*/
                Excel.Range range = worksheet.UsedRange;
 
                object[,] value = (object[,])range.get_Value(Excel.XlRangeValueDataType.xlRangeValueDefault);
                int rows = value.GetLength(0);
                for (int i = 1; i < = value.GetLength(0); i++)
                {
                    dt.Rows.Add();
                    for (int j = 1; j <= value.GetLength(1); j++)
                    {
                        dt.Rows[dt.Rows.Count - 1][j - 1] = value[i, j];
                    }
                }
 
                //This line is very important. Otherwise you'll end up with N excel processes
                ExcelObj.Quit();
            }            
            dataGridView1.DataSource = dt;
        }
    }
}
Tagged as: , No Comments