Microsoft’s Restrictive License and Viewable Base Class Libraries

I’m very worried about the announcement yesterday by Microsoft that the source to some of the .NET Base Class Libraries will be available to developers. Mostly I am worried about the impact to the Mono project and how many more developers will now be tainted by viewing the MS code. Miguel has some thoughts on that here. Also, I am worried about the viewable code, strictly from a copyright point of view.

Drawing Parallels

Everything regarding the viewable code is subject to the exact same copyright as another expressive media which is far more well known, Music. I use the word viewable because the code is neither open source or free software. Imagine that one of the “big four”, Sony, BMG, EMI, or Universal, announced that they were going to make all of their most popular music available for free listening.

The company still retains all of the copyrights. The tools to make derivative music is still up to the new artist to obtain and use. The music industry is just encouraging every musician to listen to all of this popular music… but don’t use it!

What is fair use? Remember Vanilla Ice and his uncredited David Bowie sample? He got sued and lost due to copyright violation, for a 5 second drum loop! That isn’t fair use!

So effectively we have history to tell us that if we copy and paste or republish in any way 5 lines of code from this Microsoft source code release, we are violating copyright on the code! This scares me!

I read a lot of software blogs. Check out my bloglines blogroll. I see a lot of source code in blog postings. With this Microsoft viewable code release I dread all of the upcoming posts of copy and pasted code saying “look how Microsoft did it, we can just change this one thing for our implementation”. These will all be copyright violations. These hypothetical posts will violate Microsoft’s copyright on this viewable code and it will be up to Microsoft as the copyright holder to enforce their copyright. IANAL, but I have heard that bad things can happen to copyright holders if it can be shown that they did not attempt to enforce their copyrights.

After AACS last night, some friends and I were sitting around ABC and discussing this. Some people mentioned DMCA and others mentioned patents. These are important issues, but this is not what I’m talking about. I’m talking about good old copyright. See Lawrence Lessig‘s Free Culture for a good background.

Here is an example:

After Scott Guthrie’s original announcement, which included a screenshot of Visual Studio in the debugger stepping into some BCL code, Chris Sells deep linked Scott’s screenshot. Now Chris works for Microsoft too, so maybe he had permission, but maybe he didn’t.

My worry is what happens if I do this?

Take three cases.

  1. I could link to just Scott’s screenshot with the copyright code. Am I now violating Microsoft’s copyright? No you say? Thousands of websites were shut down in years past for linking to mp3s on different servers. How is this different?
  2. I could actually display the screenshot inline using html img tags, but referencing Scott’s server. 
  3. I could transpose the code in the screenshot, thus creating a copy and definitely and blatantly violating the copyright.

I’m actually too afraid to do #3. I think I might be able to get away with #1 and maybe #2.

What once was left to lawyers and the intellectual property experts at your corporation is now something that every developer in an organization has to think about. Every developer needs to be aware of what the license and copyright is on any piece of code they view that is not owned by them or their organization.

Of course one solution to this issue might be to only work with open source software. I’m tempted to do just that, but I find that there are far more jobs available which still use proprietary software development environments, platforms and tools. I’m OK with that. I’m willing to be aware of intellectual property issues in order to work on these platforms. In my experience, other developers are hardly aware of these issues. Unlicensed and therefore entirely copyrighted code is copied out of webpages and blogs and directly into production software in both large enterprises and small businesses. With the release of viewable BCL from Microsoft, these practices become greatly more dangerous.

Copying from the web is a copyright violation

It is all over the .NET world. Today, scottgu announced that Microsoft would be making their base class library implementation source available for free (not open source or free as in freedom or free software) to developers to look at. Details are at scottgu’s blog.

It is a damn shame about the terms of the Microsoft Reference license. This means that if I want to use a bit of the source, that I cannot. For example, I want to customize DataGridViewTextBoxCell’s Paint implementation, I cannot copy the bits of the Paint implementation of DataGridViewTextBoxCell implementation and base classes to tweak my changes. I CAN read what they did, and use this new knowledge in my implementation, but any copy/paste is not allowed! Most programmers I talk to do not understand this. This is going to be a copyright nightmare for most development organizations.

We talked a bit about copyrights and open source at the SRT Solutions open house a couple of weeks ago. I was amazed when all of the programmers I talked to said “huh?” or “really?” when I said that using code they found on the Internet or a blog is a copyright violation unless that code has an express license released with it. This means much of the code at codeproject.com is not available for reuse, only for reading. This means code posted to most blogs. Of course I’m talking about the United States of America, which is where I, and all these programmers, live and work.

Programmers: Look for a license. If there isn’t one, contact the author. Ask them to release the code they put on their blog under the public domain, or creative commons license, or a permissive (BSD, MIT/X) open source license.

If you got the code from a website which specializes in programming examples, such as codeproject, contact that author. Ask them what the license is.

If you are using sample code from a vendor, make sure you are clear on what the license is. Remember, if it is not explicitly stated, then you have ZERO right to reuse that copyright code. I’ve run into to Microsoft sample code releases like this.

Luckily there are places on the internet that ALWAYS attach a license to code. Sourceforge and CodePlex are wonderful for this.

Finally, if you are a copy and paste programmer, you may want to turn off this new Visual Studio 2008 source code feature.

GO FALLEN ROGUE!

http://feeds.feedburner.com/~r/fallenrogue/~3/162666308/222-Open-Letter-to-the-people-who-present-for-Microsoft-Regional-Events

Sweet Lord he is dead on!

…it’s time for Microsoft to stop lowering the bar for developers. It’s time to stop going out on the road and saying you’ve got a presentation for software architects and then spend 2 hours describing features to your most expensive technologies. We’re architects. Price of admission should be: not an idiot or not person who doesn’t know what this thing the “internet” is. Look, I’ve had an assload of your f-ing terrible presentations and demos. Show me something, anything at all worth looking at and maybe I’ll be able to not hang my head in shame every time the term Microsoft comes up.

I hung me head in shame only a few times yesterday at Ohio Linux Fest. That is mostly because every time I said I was a .NET Developer I could at least say that I try to stay on top of Mono and run at least some of the things I write in Mono. Luckily, there weren’t too many developers at OLF. When the Ruby guy, Luke, author of Puppet, asked me “Why would anyone develop in Mono?” I was able to say “why wouldn’t you?” And of course it is easy to site the low memory space usage of a GTK application in Mono vs. Python GTK or Ruby GTK.

Its all Michael Eaton’s fault for pledging his allegiance

http://michaeleatonconsulting.com/blog/archive/2007/09/28/political-messing-with-the-pledge-of-allegiance.aspx

Like most of my U.S. readers, I grew up saying the Pledge — 13 years of public education. 13 years of saying the Pledge. 13 years of never once seeing anything wrong with it. 13 years of thinking it’d ever change.

But why pledge allegiance to the flag every day?

I don’t pledge my allegiance to my wife and child or even my Christian God every day! I do it by action.

I pledge allegiance to logic, wisdom and God and I pray that the flag falls under one of those things. But if “the flag” (this nation) is stupid, evil, and wrong. Then it does not have my allegiance.

Lets think about the very concept of requiring students to recite this. You are requiring that they make a statement about their allegiance. Merriam-Webster defines allegiance as

1 a : the obligation of a feudal vassal to his liege lord b (1) : the fidelity owed by a subject or citizen to a sovereign or government (2) : the obligation of an alien to the government under which the alien resides
2 : devotion or loyalty to a person, group, or cause

I thought this was America, land of the free and home of the brave. I do not believe that allegiance and obligation of a feudal vassal (the student) to his liege lord (the state) mixes with the liberty on which this country was founded. Take a huge step back for a minute and compare this requirement to the watching of the media broadcasts in Orwell’s 1984. I don’t see it as being so different.

Instead of requiring students to recite some fascist poem how about teaching them to think freely, make their own decisions, take responsibility and be accountable for themselves and their actions.

Quote of the Day

from http://feeds.feedburner.com/~r/AyendeRahien/~3/160655144/JAOO–Day-I.aspx

I like the ideas being evolved in Ruby, but I have reservation about doing development there. I know that I say this as a person who didn’t do any real project there, but I have the feeling that Ruby isn’t for me. Basically doesn’t provide a significant advantage over what I already have in .Net

Wow. I have to say I agree, but I couldn’t spell it out the same as Oren does. I don’t think my reasons are exactly the same, since I deal very little with profiling, debugging, deployment and monitoring.

With all the talk about Ruby recently in the .NET community, I find this quote to be refreshing. Some people are suggesting that we just jump ship and join the Ruby camp like many Java people did over the past few years. Jeremy Miller write a little bit about it recently in some thoughts about ALT.NET.

More than one person has questioned whether or not the ALT.NET canon and crowd is inevitably destined for Ruby and Ruby on Rails.  There’s some thought, and at this point observation, that the alpha geeks in .Net are starting to drift into Ruby development instead.

Right now, for me, the major frustration is what Jeremy calls “MSDN way”. Bad (non-alt.net) development is done by many shops whose development environment consists of ONLY a Visual Studio installation. Not using excellent open tools like NUnit or MbUnit, StructureMap or Windsor, NMock or RhinoMocks, and others, these are the reasons why the “alpha geeks” are jumping to Ruby. Right now the average Ruby developer understands using open libraries. Most .NET shops don’t seem to think this way. We need to change our way of thinking.

DataGridView File Name Path Columns

Remember that ancient technology known as Winforms. No? Remember that .NET namespace called System.Windows.Forms? You know the one. The one that Mono got to 99% completion right around the time that it became the less cool way to write apps and GTK# and WPF became the cools ways.

Anyway, I’ve been writing more Windows Forms code recently and I had a need to display a full path file name in a column. That is no problem, just data bind. But I wanted a little bit better experience for my users. Instead of a column displaying “C:\Documents and Sett…” when a column isn’t wide enough, I want it to say “…\MyFile.Txt”. It turns out that thanks to the closed nature of some things DataGridView, this isn’t all that easy.

The nice part is that the framework has a TextFormatFlags Enumeration which includes a PathEllipsis value. The not as nice part is that TextRenderer.DrawText is what uses this value. This means that we aren’t using a nice Control, we are instead drawing ourselves using GDI. *sigh* If only DataGridViewTextBoxColumn took a TextFormatFlags argument.

The solution?

Your own DataGridViewColumn and DataGridViewCell types.

Form1

See what I mean?

The cool part was that once we were doing custom painting it was pretty easy to go one step further and optionally allow browsing for a file path when a button is clicked in the cell.

Form1 (2)

Creating the column class is pretty easy.

 

/// <summary>
/// Hosts a collection of DataGridViewTextBoxCell cells.
/// </summary>
public class DataGridViewFilePathColumn : DataGridViewColumn
{
    private bool showBrowseButton;
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    [DefaultValue(false)]
    [Category(“Appearance”)]
    [Description(“Show a button in each cell for browsing for files.”)]
    public bool ShowBrowseButton
    {
        get { return showBrowseButton; }
        set
        {
 
            showBrowseButton = value;
        }
    }
 
    private bool useOpenFileDialogOnButtonClick;
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    [DefaultValue(false)]
    [Category(“Behavior”)]
    [Description(“OpenFileDialog is dispalyed and on success the contents of the Cell is replaced with the new file path.”)]
    public bool UseOpenFileDialogOnButtonClick
    {
        get { return useOpenFileDialogOnButtonClick; }
        set
        {
            useOpenFileDialogOnButtonClick = value;
        }
    }
    public DataGridViewFilePathColumn()
        : base(new DataGridViewFilePathCell())
    {
    }
    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            if (null != value &&
                !value.GetType().IsAssignableFrom(typeof(DataGridViewFilePathCell)))
            {
                throw new InvalidCastException(“must be a DataGridViewFilePathCell”);
            }
            base.CellTemplate = value;
        }
    }
}

/// <summary>
/// Displays editable text information in a DataGridView control. Uses
/// PathEllipsis formatting if the column is smaller than the width of a
/// displayed filesystem path.
/// </summary>
public class DataGridViewFilePathCell : DataGridViewTextBoxCell
{
    Button browseButton;
    Dictionary<Color, SolidBrush> brushes = new Dictionary<Color, SolidBrush>();
    protected virtual SolidBrush GetCachedBrush(Color color)
    {
        if (this.brushes.ContainsKey(color))
            return this.brushes[color];
        SolidBrush brush = new SolidBrush(color);
        this.brushes.Add(color, brush);
        return brush;
    }
 
    protected virtual Button GetBrowseButton(bool wireOpenFileDialog)
    {
        if (null == browseButton)
        {
            browseButton = new Button();
            browseButton.Text = “…”;
            browseButton.Click += new EventHandler(browseButton_Click); //yes, really two event handlers!
            if (wireOpenFileDialog)
                browseButton.Click += new EventHandler(delegate(object sender, EventArgs e)
                {
                    if (this.RowIndex >= 0)
                    {
                        using (OpenFileDialog ofd = new OpenFileDialog())
                        {
                            if (System.IO.File.Exists((string)this.Value))
                                ofd.InitialDirectory = System.IO.Path.GetDirectoryName((string)this.Value);
                            if (ofd.ShowDialog() == DialogResult.OK)
                            {
                                this.Value = ofd.FileName;
                            }
                        }
                    }
                });
        }
        return browseButton;
    }
 
    protected virtual bool RightToLeftInternal
    {
        get
        {
            return this.DataGridView.RightToLeft == RightToLeft.Yes;
        }
    }
    protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
    {
        if (cellStyle == null)
        {
            throw new ArgumentNullException(“cellStyle”);
        }
        this.PaintPrivate(graphics, clipBounds, cellBounds, rowIndex, cellState, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
    }
 
    protected Rectangle PaintPrivate(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
    {
        //System.Diagnostics.Debug.WriteLine(string.Format(“Painting Cell row {0} for rowindex {2} with rectangle {1}”, this.RowIndex, cellBounds, rowIndex));
        SolidBrush cachedBrush;
        Rectangle empty = Rectangle.Empty;
        if (PaintBorder(paintParts))
        {
            this.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle);
        }
        Rectangle rectangle2 = this.BorderWidths(advancedBorderStyle);
        Rectangle borderedCellRectangle = cellBounds;
        borderedCellRectangle.Offset(rectangle2.X, rectangle2.Y);
        borderedCellRectangle.Width -= rectangle2.Right;
        borderedCellRectangle.Height -= rectangle2.Bottom;
        Point currentCellAddress = base.DataGridView.CurrentCellAddress;
        bool isFirstCell = (currentCellAddress.X == base.ColumnIndex) && (currentCellAddress.Y == rowIndex);
        bool flagisFirstCellAndNotEditing = isFirstCell && (base.DataGridView.EditingControl != null);
        bool thisCellIsSelected = (cellState & DataGridViewElementStates.Selected) != DataGridViewElementStates.None;
        if ((PaintSelectionBackground(paintParts) && thisCellIsSelected) && !flagisFirstCellAndNotEditing)
        {
            cachedBrush = GetCachedBrush(cellStyle.SelectionBackColor);
        }
        else
        {
            cachedBrush = GetCachedBrush(cellStyle.BackColor);
        }
        if (((PaintBackground(paintParts)) && ((cachedBrush.Color.A == 0xff) && (borderedCellRectangle.Width > 0))) && (borderedCellRectangle.Height > 0))
        {
            graphics.FillRectangle(cachedBrush, borderedCellRectangle);
        }
        if (cellStyle.Padding != Padding.Empty)
        {
            if (RightToLeftInternal)
            {
                borderedCellRectangle.Offset(cellStyle.Padding.Right, cellStyle.Padding.Top);
            }
            else
            {
                borderedCellRectangle.Offset(cellStyle.Padding.Left, cellStyle.Padding.Top);
            }
            borderedCellRectangle.Width -= cellStyle.Padding.Horizontal;
            borderedCellRectangle.Height -= cellStyle.Padding.Vertical;
        }
        if (((isFirstCell) && (!flagisFirstCellAndNotEditing && PaintFocus(paintParts))) && ((ShowFocusCues && base.DataGridView.Focused) && ((borderedCellRectangle.Width > 0) && (borderedCellRectangle.Height > 0))))
        {
            ControlPaint.DrawFocusRectangle(graphics, borderedCellRectangle, Color.Empty, cachedBrush.Color);
        }
        Rectangle cellValueBounds = borderedCellRectangle;
        string text = formattedValue as string;
        if ((text != null) && (!flagisFirstCellAndNotEditing))
        {
            int y = (cellStyle.WrapMode == DataGridViewTriState.True) ? 1 : 2;
            borderedCellRectangle.Offset(0, y);
            borderedCellRectangle.Width = borderedCellRectangle.Width;
            borderedCellRectangle.Height -= y + 1;
            if ((borderedCellRectangle.Width > 0) && (borderedCellRectangle.Height > 0))
            {
                TextFormatFlags flags = //DataGridViewUtilities.ComputeTextFormatFlagsForCellStyleAlignment(base.DataGridView.RightToLeftInternal, cellStyle.Alignment, cellStyle.WrapMode);
                    TextFormatFlags.PathEllipsis;
 
                if (PaintContentForeground(paintParts))
                {
                    if ((flags & TextFormatFlags.SingleLine) != TextFormatFlags.GlyphOverhangPadding)
                    {
                        flags |= TextFormatFlags.EndEllipsis;
                    }
                    DataGridViewFilePathColumn filePathColumn = (DataGridViewFilePathColumn)this.DataGridView.Columns[ColumnIndex];
                    if (true && filePathColumn.ShowBrowseButton)
                    {
                        if (this.RowIndex >= 0)
                        {
                            Button browseButton = GetBrowseButton(filePathColumn.UseOpenFileDialogOnButtonClick);
                            bool changed = false;
                            if ((browseButton.Width != Math.Max(10, borderedCellRectangle.Width / 4)) && (browseButton.Width != 20))
                            {
                                System.Diagnostics.Trace.WriteLine(string.Format(“browseButton Width was incorrect:{0} for given rectangle:{1}”, browseButton.Width, borderedCellRectangle));
                                browseButton.Width = Math.Max(10, borderedCellRectangle.Width / 4);
                                browseButton.Width = Math.Min(browseButton.Width, 20);
                                changed = true;
                            }
                            if (browseButton.Height != (borderedCellRectangle.Height – 4))
                            {
                                System.Diagnostics.Trace.WriteLine(string.Format(“browseButton Height was incorrect:{0} for given rectangle:{1}”, browseButton.Height, borderedCellRectangle));
                                browseButton.Height = borderedCellRectangle.Height – 4;
                                changed = true;
                            }
                            Point loc = new Point();
                            loc.X = borderedCellRectangle.X + borderedCellRectangle.Width – browseButton.Width;
                            loc.Y = borderedCellRectangle.Y;
                            if (browseButton.Location != loc)
                            {
                                System.Diagnostics.Trace.WriteLine(string.Format(“browseButton location was incorrect:{0} for given rectangle:{1} with loc: {2}”, browseButton.Location, borderedCellRectangle, loc));
                                browseButton.Location = loc;
                                changed = true;
                            }
                            if (changed)
                                browseButton.Invalidate();
                            if (!this.DataGridView.Controls.Contains(browseButton))
                                this.DataGridView.Controls.Add(browseButton);
                            borderedCellRectangle.Width -= browseButton.Width;
                        }
                    }
                    TextRenderer.DrawText(graphics, text, cellStyle.Font, borderedCellRectangle, thisCellIsSelected ? cellStyle.SelectionForeColor : cellStyle.ForeColor, flags);
                }
 
            }
        }
        if ((base.DataGridView.ShowCellErrors) && PaintErrorIcon(paintParts))
        {
            PaintErrorIcon(graphics, cellStyle, rowIndex, cellBounds, cellValueBounds, errorText);
        }
        return empty;
    }
 
 
    void browseButton_Click(object sender, EventArgs e)
    {
        this.RaiseCellClick(new DataGridViewCellEventArgs(this.ColumnIndex, this.RowIndex));
    }
 
    protected virtual Rectangle ComputeErrorIconBounds(Rectangle cellValueBounds)
    {
        if ((cellValueBounds.Width >= 20) && (cellValueBounds.Height >= 0x13))
        {
            return new Rectangle(RightToLeftInternal ? (cellValueBounds.Left + 4) : ((cellValueBounds.Right – 4) – 12), cellValueBounds.Y + ((cellValueBounds.Height – 11) / 2), 12, 11);
        }
        return Rectangle.Empty;
    }
 
 
 
    protected virtual void PaintErrorIcon(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Rectangle cellBounds, Rectangle cellValueBounds, string errorText)
    {
        if ((!string.IsNullOrEmpty(errorText) && (cellValueBounds.Width >= 20)) && (cellValueBounds.Height >= 0x13))
        {
            Rectangle iconBounds = this.GetErrorIconBounds(graphics, cellStyle, rowIndex);
            if ((iconBounds.Width >= 4) && (iconBounds.Height >= 11))
            {
                iconBounds.X += cellBounds.X;
                iconBounds.Y += cellBounds.Y;
                PaintErrorIcon(graphics, iconBounds);
            }
        }
    }
 
    protected static void PaintErrorIcon(Graphics graphics, Rectangle iconBounds)
    {
        Bitmap errorBitmap = new Bitmap(typeof(DataGridViewCell), “DataGridViewRow.error.bmp”);
        errorBitmap.MakeTransparent();
        if (errorBitmap != null)
        {
            lock (errorBitmap)
            {
                graphics.DrawImage(errorBitmap, iconBounds, 0, 0, 12, 11, GraphicsUnit.Pixel);
            }
        }
    }
 
    protected static bool PaintErrorIcon(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.ErrorIcon) != DataGridViewPaintParts.None);
    }
    protected static bool PaintFocus(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.Focus) != DataGridViewPaintParts.None);
    }
    protected static bool PaintBackground(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.Background) != DataGridViewPaintParts.None);
    }
    protected static bool PaintBorder(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.Border) != DataGridViewPaintParts.None);
    }
    protected static bool PaintContentBackground(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.ContentBackground) != DataGridViewPaintParts.None);
    }
    protected static bool PaintContentForeground(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.ContentForeground) != DataGridViewPaintParts.None);
    }
    protected static bool PaintSelectionBackground(DataGridViewPaintParts paintParts)
    {
        return ((paintParts & DataGridViewPaintParts.SelectionBackground) != DataGridViewPaintParts.None);
    }
 
    public bool ShowFocusCues
    {
        get { return true; }
    }
 
    protected bool ApplyVisualStylesToHeaders
    {
        get
        {
            if (Application.RenderWithVisualStyles)
            {
                return this.DataGridView.EnableHeadersVisualStyles;
            }
            return false;
        }
    }
 
    public DataGridViewFilePathCell()
        : base()
    {
    }
}

The DataGridViewCell drived class is a little more challenging. It turns out we can inherit from DataGridViewTextBoxCell and get its editing capabilities, but rendering the Text in non-editing mode of a cell is entirely our responsibility. Welcome to the disgusting world of control drawing.

 

Side Note:

I just tried Switcher 2.0 beta thanks to Jeff Atwood’s suggestion and YES! YES! YES! This is what managing multiple windows SHOULD be!

Other Side Note:

How many times can I misspell feisty as fiesty?

Thanks to Dustin Campbell for the AWESOME code formatting tips.

Ubuntu old school

So I run into this old server and I realize it had DAPPER on it still! After running updates I just can’t take it any longer. I try the update-manager upgrade method and then I remember (with help of google) that this didn’t always work for dapper->edgy. So I vim “:%s/dapper/edgy/g” the sources.list file and apt-get update && apt-get -f dist-upgrade

Luckily this thing is only a server and so I only get this prompt:

787 upgraded, 148 newly installed, 58 to remove and 31 not upgraded.
Need to get 469MB of archives.

Over a lowly T1 its only estimating 45 minutes. Not bad. *sigh* Then I’ll have to go edgy->feisty and next week I’ll probably find myself moving it to gutsy.

No power in the nets can stop me.

This customer of mine gets Internet access through their building. It is nice for them because they are a tiny company and don’t have time or money to run much of their own network. Unfortunately, doing this Ubuntu upgrade for them was not trivial. They have a WatchGuard firewall which is configured pretty dumb. When I try to apt-get upgrade, I get a failure.

Apt-get didn’t give me much detail other than a failure message, but trying to access a deb in my browser gives good information:

Response denied by […] WatchGuard Firewall HTTP proxy.

Reason: header ‘Content-Type’ denied rule=’Default’ value=’application/x-debian-package’

WOW! Debian denied!

This was easy enough to work around by using proxies. Thank goodness apt supports http_proxy. But thank a friend even more for a favor so that I could proxy through a faster connection and max out the customers T1 rather than proxy through my cable modem and only get cable modem outbound speeds. 180kB > 40kB

I won’t be getting an iPod

When Apple updated their iPod lineup a couple of weeks ago, I started to drool. I’ve wanted an 80G iPod for a while, but when I finally had the opportunity to buy one – May of this year – I recognized that the then current generation of iPod had been around for a while and that a new one would be out soon.

This really pisses me off: New iPods reengineered to block synching with Linux

The 160G iPod “classic” and the iTouch are a couple of really sexy sweet products. However, I wouldn’t buy a toaster oven that I couldn’t empty the crumb tray, and I won’t buy an iPod from a company that intentionally limits my ability to use their product.

Zune is lame at 30G. Creative has some offerings but Apple is so far ahead simply in user experience. This is why I wanted an iPod over something else. If you have never used an iPod for more than 5 minutes that you don’t know what you are missing. Maybe this is a case of ignorance is bliss. I might have been happy with a Creative product if I had never touched an iPod. Alas, I have touched an iPod. The menu system and “click wheel” interface are superb.

-ANGRY

DataGridView DataBound Copy, Paste, Drag, Drop

I’m doing more Windows Forms programming these day and I am very surprised to see the lack of documentation on the web regarding DataGridView and copy and paste as well as drag and drop. Not even the DataGridView FAQ – an excellent resource – covers copy and paste or drag and drop other than the ClipboardCopyMode.

With a little code you can enhance the copy and paste as well as implement drag on drop for selected rows in a DataGridView. First thing, all users think differently. Some use toolbars. some use context menus. Some use window menus. I’ll leave the window menus to the reader.

If you have a toolbar or a binding navigator, add cut, copy, and paste icons to the bar. The easiest way I have found is to add a ToolStrip control, use the “add standard items” and then copy the three ToolStripButtons and delete the unneeded ToolStrip control.

Add a ContextMenuStrip and add Cut, Copy and Paste to it. Wiring up events is pretty easy.

 

        private void cutToolStripMenuItem_Click(object sender, EventArgs e)

        {

            this.cutToClipboard();

        }

 

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)

        {

            this.copyToClipboard();

        }

 

        private void pasteToolStripMenuItem_Click(object sender, EventArgs e)

        {

            this.pasteFromClipboard();

        }

 

        private void cutToolStripButton_Click(object sender, EventArgs e)

        {

            cutToClipboard();

        }

 

        private void copyToolStripButton_Click(object sender, EventArgs e)

        {

            copyToClipboard();

        }

 

        private void pasteToolStripButton_Click(object sender, EventArgs e)

        {

            pasteFromClipboard();

        }

Implementing the actual methods is pretty easy. The trick is to use both DataGridView’s GetClipboardContent as well as your own DataFormat.

        private DataFormats.Format employeeDataFormat = DataFormats.GetFormat(“employeeDataFormat”);

        private void deleteCurrentSelection()

        {

            this.employeeBindingSource.RemoveCurrent();

        }

        private void cutToClipboard()

        {

            copyToClipboard();

            deleteCurrentSelection();

        }

        private void copyToClipboard()

        {

            IDataObject clipboardData = getDataObject();

            Clipboard.SetDataObject(clipboardData);

        }

        private IDataObject getDataObject()

        {

            //TODO: why does this return plaintext(tsv) and CSV that has empty first column?

            DataObject clipboardData = this.dataGridView1.GetClipboardContent();

 

            Employee[] employees =

                new Employee[dataGridView1.SelectedRows.Count];

            for (int i = 0; i < dataGridView1.SelectedRows.Count; i++)

            {

                employees[i] = dataGridView1.SelectedRows[i].DataBoundItem as Employee;

            }

            clipboardData.SetData(employeeDataFormat.Name, employees);

            return clipboardData;

        }

        private void pasteEmployeeDataObject(IDataObject dataObject)

        {

            object employeeDataObject = dataObject.GetData(employeeDataFormat.Name);

            if (employeeDataObject != null)

            {

                Employee[] employees = (Employee[])employeeDataObject;

                foreach (Employee employee in employees)

                {

                    bindingRowData.Add(employee);

                }

            }

        }

        private void pasteCsv(IDataObject dataObject)

        {

            object lDataObjectGetData = dataObject.GetData(DataFormats.CommaSeparatedValue);

            string csv = lDataObjectGetData as string;

            if (csv==null)

            {

                System.IO.MemoryStream stream = lDataObjectGetData as System.IO.MemoryStream;

                if (stream!=null)

                    csv = new System.IO.StreamReader(stream).ReadToEnd();

            }

            if (csv==null)

                return;

            string[] lines = csv.Split(‘\n’);

            int row = dataGridView1.NewRowIndex;

            int col = dataGridView1.CurrentCell.ColumnIndex;

            foreach (string line in lines)

            {

                if (row < dataGridView1.RowCount && line.Length > 0)

                {

                    try

                    {

                        string[] cells = line.Split(‘,’);

                        for (int i = 0; i < cells.Length

                            ; ++i)

                        {

                            if (col + i < this.dataGridView1.ColumnCount)

                            {

                                dataGridView1[col + i, row].Value =

                                    Convert.ChangeType(cells[i], dataGridView1[col + i, row].ValueType);

                            }

                            else

                            {

                                break;

                            }

                        }

                        row++;

                    }

                    catch (FormatException)

                    { //TODO: log exceptions using a nice standard logging library 🙂

                        dataGridView1.CancelEdit();

                    }

                }

                else

                {

                    break;

                }

            }

        }

        private void pasteFromClipboard()

        {

            if (dataGridView1.SelectedRows.Contains(dataGridView1.Rows[dataGridView1.NewRowIndex]))

            {

                dataGridView1.Rows[dataGridView1.NewRowIndex].Selected = false;

                dataGridView1.CancelEdit();

            }

            IDataObject clipboardData = Clipboard.GetDataObject();

            if (Clipboard.ContainsData(employeeDataFormat.Name))

            {

 

                pasteEmployeeDataObject(clipboardData);

            }

            else if (Clipboard.ContainsData(DataFormats.CommaSeparatedValue))

            {

 

                pasteCsv(clipboardData);

            }

        }

The cool thing about the different paste methods and getObject method is that they work well with drag and drop as well.

Accepting drops is easy. It is totally straight forward just like it is with other windows forms controls.

        private void dataGridView1_DragEnter(object sender, DragEventArgs e)

        {

 

            if (e.Data.GetDataPresent(employeeDataFormat.Name) || e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))

            {

                e.Effect = DragDropEffects.Copy;

 

            }

            else

                e.Effect = DragDropEffects.None;

        }

 

        private void dataGridView1_DragDrop(object sender, DragEventArgs e)

        {

            if (e.Effect == DragDropEffects.Copy)

            {

                string[] formats = e.Data.GetFormats();

                if (e.Data.GetDataPresent(employeeDataFormat.Name))

                {

                    pasteEmployeeDataObject(e.Data);

                }

                else if (e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))

                {

                    pasteCsv(e.Data);

                }

            }

        }

Handling drags is a little more challenging. DataGridView doesn’t have the ItemDrag event. Total bummer. It turns out implementing drag detection is very easy with a simple user32.dll function call called – amazingly enough – DragDetect.

        [System.Runtime.InteropServices.DllImport(“user32.dll”)]

        private static extern bool DragDetect(IntPtr intPtr, Point point);

 

        Point dragEnterPoint;

 

        private void dataGridView1_MouseMove(object sender, MouseEventArgs e)

        {

            if (e.Button == MouseButtons.None)

            {

                this.dataGridView1.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);

            }

            DataGridView.HitTestInfo info = dataGridView1.HitTest(e.X, e.Y);

            bool lDragDetect = DragDetect(this.Handle, dragEnterPoint);

            if (lDragDetect)

            {

                if (info.RowIndex >= 0)

                {

                    IDataObject dataObject = getDataObject();

                    if (dataObject != null)

                        dataGridView1.DoDragDrop(dataObject, DragDropEffects.Copy);

                }

            }

        }

 

        private void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)

        {

            if (e.Button == MouseButtons.Left)

            {

                if (e.ColumnIndex >=0 )

                {

                    dragEnterPoint = new Point(e.X, e.Y);

                    this.dataGridView1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);

                }

            }

        }

 

        private void dataGridView1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)

        {

            this.dataGridView1.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.dataGridView1_MouseMove);

        }

A couple things are going on here. We don’t want to change too much behavior of the DataGridView. Dragging in the RowHeader normally highlights multiple rows. So in the  CellMouseDown handler we check that ColumnIndex is non-negative. ColumnIndex is -1 if the mouse was down in the RowHeader.

I’m not using the DataGridView Click or MouseDown events because they would prevent those events from getting down to the cells and prevent normal cell editing behavior. The MouseMove event is used to call DragDetect but the event is only registered and fired if the mouse button is down. I don’t want to be firing lots of pointless MouseMove events. This works well enough.

Finally DoDragDrop is called with the same getObject result as a copy uses.

Once catch. DataGridView does implement its own copy if you press ctrl-c. This means that the current row gets put onto the clipboard, but it isn’t using my copy method. What is worse is that if my copy method is used, then I can do something really cool like copy, then reorder the columns and paste. Since it is using the custom DataFormat and just adding to a databound list, everything pastes into the proper columns. The alternative is that things paste into the wrong columns or worse, you get an exception because the datatypes don’t match. Oddly, DataGridView doesn’t implement paste via ctrl-v. This is trivially rectified.

        private void dataGridView1_KeyDown(object sender, KeyEventArgs e)

        {

            if (e.KeyCode == Keys.C && e.Control)

            {

                this.copyToClipboard();

                e.Handled = true; //otherwise the control itself tries to “copy”

            }

            if (e.KeyCode==Keys.v && e.Control)

            {

                this.pasteFromClipboard();               

            }

        }

Overall the result is nice. You can copy and drag rows within the control itself, between multiple instances of the same DataGrid. You can drag and copy and paste between Excel. Its all good.

How Not To: Write Documentation

Check out this “How to” over at MSDN. Be sure to look at the code example. You mono guys can go to. I’ll wait.

Ok, did you laugh?

It is as if the code example wasn’t even looked at by someone who can program!

I’m still laughing about it.

I think I’ll write my own similar documentation.

public class HowNotTo {
WriteDocumentation(That,Does,Not,Even,Compile);
ShootSelf(InTheHead);
}