加班一个月了,昨天晚上终于告一段落。有了一点点收获,跟大家分享一下。
作为一个程序员,最忌讳两件事:只用剪刀和胶水来写程序;想到哪写到哪。copy和paste是必须用的,但不能不用脑子。东拼西凑的结果就是代码混乱,无法保证质量,甚至会有隐藏在水下的bug——网络上很多代码质量参差不齐。教科书从小就教育我们,舶来品要扬弃,去粗取精。因此,无论你找到的样例代码是多么匹配你的需求,你必须作的事情就是,研究它,然后再加以改进以融合到你的代码里。
关于这点,我自认为执行得很好。本月我总共分了4个任务,其中最后一个是对tabcontrol里的tab page实现拖放功能,用C#开发。MSDN是个好东西,MS的文档做的很好——但这不会降低我对MS的憎恨。翻了一天的MSDN,很多control的拖放的实现,就是没有tabcontrol的,因为它比较特殊。于是在网上找到了一个,阿良。NET,给出了实现方法和代码。其基本思想是继承一个tabcontrol,自己做一个带拖放功能的tabcontrol,主要代码及我做的改动一一列出(不同之处用绿色标明。当小tab移动到大tab上时,由于交换tab位置导致不停的闪烁,蓝色代码就是为了消除这种闪烁,当然还有别的实现):
=======================================================================
// Raise the MouseDown event
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Point pt = new Point(e.X, e.Y);
TabPage tp = GetTabPageByTab(pt);
if(tp != null)
{
DoDragDrop(tp, DragDropEffects.All);
}
}
因为我不能创建新类——这需要很高层的领导批准的,所以就把该方法的实现放到MouseDown事件处理方法中,如下:
// My code
private List rectList = new List();
private void tabControl1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) // 只响应左键事件
{
TabPage aTabPage = tabControl1.SelectedTab; // 不必像阿良的实现那么麻烦
if (aTabPage != null)
{
tabControl1.DoDragDrop(aTabPage, DragDropEffects.Move);
for (int i = 0; i < tabControl1.TabPages.Count; ++i)
{
rectList.Add(tabControl1.GetTabRect(i);
}
}
}
}
=======================================================================
// Raise the DragOver event
protected override void OnDragOver(System.Windows.Forms.DragEventArgs e)
{
base.OnDragOver(e);
Point pt = new Point(e.X, e.Y);
//We need client coordinates.
pt = PointToClient(pt);
//Get the tab we are hovering over.
TabPage hover_tab = GetTabPageByTab(pt);
//Make sure we are on a tab.
if(hover_tab != null)
{
//Make sure there is a TabPage being dragged.
if(e.Data.GetDataPresent(typeof(TabPage)))
{
e.Effect = DragDropEffects.Move;
TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
int item_drag_index = FindIndex(drag_tab);
int drop_location_index = FindIndex(hover_tab);
//Don't do anything if we are hovering over ourself.
if(item_drag_index != drop_location_index)
{
ArrayList pages = new ArrayList();
//Put all tab pages into an array.
for(int i = 0; i < TabPages.Count; i++)
{
//Except the one we are dragging.
if(i != item_drag_index)
pages.Add(TabPages[i]);
}
//Now put the one we are dragging it at the proper location.
pages.Insert(drop_location_index, drag_tab);
//Make them all go away for a nanosec.
TabPages.Clear();
//Add them all back in.
TabPages.AddRange((TabPage[])pages.ToArray(typeof(TabPage)));
//Make sure the drag tab is selected.
SelectedTab = drag_tab;
}
}
}
else
{
e.Effect = DragDropEffects.None;
}
}
// My code. Implement DragOver event handler
private void tabControl1_DragOver(object sender, DragEventArgs e)
{
Point aPoint = new Point(e.X, e.Y);
//We need client coordinates.
aPoint = tabControl1.PointToClient(aPoint);
//Get the tab we are hovering over.
TabPage hoveringTabPage = GetTabPageByTab(aPoint);
//Make sure we are on a tab.
if (hoveringTabPage != null)
{
//Make sure there is a TabPage being dragged.
if (e.Data.GetDataPresent(typeof(TabPage)))
{
e.Effect = DragDropEffects.All;
TabPage draggingTabPage = (TabPage)e.Data.GetData(typeof(TabPage));
int item_drag_index = FindIndex(draggingTabPage);
int drop_location_index = FindIndex(hoveringTabPage);
//Don't do anything if we are hovering over ourself.
if (item_drag_index != drop_location_index)
{
tabControl1.TabPages.Remove(draggingTabPage);
tabControl1.TabPages.Insert(drop_location_index, draggingTabPage);
}
}
}
else
{
e.Effect = DragDropEffects.None;
}
}
最关键的改动在“if (item_drag_index != drop_location_index)”块里。阿良用了一个list来存放所有的移动之后的tab page,然后清除原先的tabcontrol中的tab page,最后将list里的tab page再添回去。这样每次鼠标移动到另一个tab上时,都要重新操作很多tab page。当tab page数量巨大,就会有性能问题。而且,我的tab page需要动态加载很多control,并且执行许多操作来加载数据,这样的话,就会带来巨大的性能损失。修改之后只剩下两行代码,性能也提高了许多。对于这两行,给个图示:
当要拖动的tab page在drop地点之前:
选定时:
-----------------------------
| tab1 | tab2 | tab3 | tab4 |
-----------------------------
︿
|拖动tab1对应的tab page
DragOver,鼠标在tab3上:
-----------------------------
| tab1 | tab2 | tab3 | tab4 |
-----------------------------
︿
|dragover()得到tab3,insert index = 2
-----------------------------
| tab2 | tab3 | tab4 | |
-----------------------------
︿
|tab1被移出
-----------------------------
| tab2 | tab3 | tab1 | tab4 |
-----------------------------
︿
|tab1被插入到index = 2的位置
当要拖动的tab page在drop地点之后:
选定时:
-----------------------------
| tab1 | tab2 | tab3 | tab4 |
-----------------------------
︿
|拖动tab4对应的tab page
DragOver,鼠标在tab2上:
-----------------------------
| tab1 | tab2 | tab3 | tab4 |
-----------------------------
︿
|dragover()得到tab2,insert index = 1
-----------------------------
| tab1 | tab2 | tab3 | |
-----------------------------
︿
|tab2被移出
-----------------------------
| tab1 | tab4 | tab2 | tab3 |
-----------------------------
︿
|tab4被插入到index = 1的位置
=======================================================================
// My code to implement DragDrop event handler.
private void tabControl1_DragDrop(object sender, DragEventArgs e)
{
tabControl1.SelectedTab = (TabPage)e.Data.GetData(typeof(TabPage));
rectList.Clear();
}
private void tabControl1_MouseUp(object sender, MouseEventArgs e)
{
if (rectList.Count != 0)
{
rectList.Clear();
}
}
=======================================================================
private TabPage GetTabPageByTab(Point aPoint)
{
TabPage aTabPage = null;
for (int i = 0; i < tabControl1.TabPages.Count; ++i)
{
if (rectList[i].Contains(aPoint))
{
aTabPage = tabControl1.TabPages[i];
}
}
return aTabPage;
}
阿良将上面的处理放到了DragOver中,其实这是正常处理。只不过,tabcontrol1实现了SelectedIndexChanged(),而这个方法里的操作也是非常影响性能的。这样我就把它挪到了DragDrop完成事件处理中。
好了,说完了copy和paste代码的问题,该说说另一个体会了。
大多数程序员,从菜鸟级到骨灰级,最容易犯的一个毛病就是:写代码时想到哪,写到哪,尽管犯这种毛病的原因很多,且不尽相同。菜鸟级一般急于看到结果以验证自己写的代码是否正确运行,骨灰级的一般可以在很短的时间内将脑袋里的想法转换成代码。
想到哪写到哪得结果就是,对于骨灰来说,顶多是加些修饰罢了,对于菜鸟来说就是灾难。在windows平台app开发领域,我就是一个不折不扣地菜鸟。前三个任务是UI方面的,第四个任务既有UI方面,又有Library的实现。对于windows GUI app的开发绝对超菜,但是时间紧迫,而MS的.NET Framework又非常庞大复杂,不容许有过多的时间去研究考虑,于是,犯毛病了——结果就是,写出来的代码一团糟,功能倒是对了,可是bug一大堆。我也想考虑好了再动手,可是MS的那套东西我实在是没概念,按照自己想法做的东西,在MS里根本不能跑。所以,痛恨MS,把windows开发人员的思维方式限定到他们的思维方式上。
后来做Library,除了编程语言,基本与MS无关,于是,在“小眼瞪大眼”研究了3天之后,花了2天时间,将代码写完并测完,一切OK!比计划还早一天完成!
总结一下,就是两点:
copy & paste代码时用点脑子。
写代码之前一定要想好。再小的代码量也要研究好,做好设计——当然,修改style之类超简单的事情就不用了,别浪费时间。
再补充一点,C#区分值类型和引用类型实在是一个很差的决定,因为没有显式的告诉programmer,哪些对象是value type,哪些是reference type,尤其是把structure作为value type,让人很容易将其和类混淆。尽管我已经很熟悉value type和reference type的概念,且完全理解了他们,但是编码过程中不可能每次都去查看某个对象是那个类型——虽然IDE会有提示——因为这种混淆,我花了30%的时间来调试问题。这可以说是C#的一个败笔吧。JAVA中所有的都是引用类型,除了native的类型如int、float。而C/C++将引用与值明显区分开(指针)。再次讨厌MS!
Copyleft (C) 2007 raof01. 本文及其代码可以用于任何用途,且不必事先征得作者同意。