审查征集贴:http://www.cnblogs.com/BeginnerClassroom/archive/2010/07/30/1788649.html

附录征集贴:http://www.cnblogs.com/BeginnerClassroom/archive/2010/08/04/1792175.html

欢迎各位园友对本书的某一部分内容进行拓展,将以附录的形式附在书后。

(PS:会署名,但无稿费,因为本来就没多少,不够分的。当然如果发了大财,我会分给大家的。)

标题 作者 状态
关于RichTextBox修改字体大小的研究 李雨来 已完稿
委托和接口的区别 汤非凡 正在写
XML格式注释 Capricornus 正在写
接口的显式实现以及与抽象类的比较 顾磊 正在写
正则表达式拓展   待选
C#程序编码规范   待选
.NET版本变更表 张智鸣 正在写
字符编码 赵士敬 正在写
     

 

 

附录 关于RichTextBox修改字体大小的研究

(本文由李雨来提供)

今天跟一个朋友探讨了一下关于RichTextBox的一个问题,具体是这样的:在WinForm编程中,有一个用于编辑文本的RichTextBox控件,它有一个SelectionFont属性,我们可以用它来设置选中文本的字体。不过这个属性有一个问题,就是当选择的文本有2个以上字体时,这个属性的值为null,因此我们无法得到原来这段文字的字体对象,也就无法修改这段文本的字体大小的同时又不改变其字体。

比如下面的代码会出现问题:

        //字号框(在组合框中输入字号,改变字体大小)
        private void sizeToolStripComboBox_TextChanged(object sender, EventArgs e)
        {            
            if (sizeToolStripComboBox.Text != "")
            {
                float fontSize = Convert.ToSingle(sizeToolStripComboBox.Text);
                changeFontSize(fontSize);
            }
        }

        private void changeFontSize(float fontsize)
        {   
            Font oldFont = richTextBox.SelectionFont;         
            richTextBox.SelectionFont = new Font(oldFont, fontsize);
        }

如果选中的文本中包含了两种字体,那么oldFont变量的值即为null,这样就会出现问题。最简单的办法加入一个条件判断来过滤这种情况。

        private void changeFontSize(float fontsize)
        {   
            Font oldFont = richTextBox.SelectionFont; 
            if (oldFont != null)        
                   richTextBox.SelectionFont = new Font(oldFont, fontsize);
        }

但是这样的话,当我们选中的文字包含两个以上字体时就无法修改字体的大小。不过解决方法不是没有,我们可以通过RichTextBox对象的SelectionStart和SelectionLength两个属性来定位选定的文本,并通过Select()方法逐个选择并设置字符。

 private void changeFontSize(float fontsize)
        {
            int selStart = this.richTextBox.SelectionStart;
            int selLen = this.richTextBox.SelectionLength;
            int selEnd = selStart + selLen;

            for (int i = selStart; i < selEnd; i++)
            {
                this.richTextBox.Select(i, 1);
                Font oldFont = richTextBox.SelectionFont;
                if (oldFont != null)
                {
                    richTextBox.SelectionFont = new Font(oldFont.Name, fontsize);
                }
            }
            //在字体之后,我们还应该重新选中最初选中的文字区域,这样才得严谨了。
            this.richTextBox.Select(selStart, selLen);
        }

上面这段代码不难理解,而且很容易想到。就算最初选中的文字区域每个字都是一种字体的话,那么针对每个文字来进行字体大小的修改也不会出现问题。

现在你可能已经满意了,不过仔细想想,这种算法的效率还是比较低的。我们还可以优化一下,就是当程序运行在最初的选定区域时,如果SelectionFont不为null,那么可以直接修改字体大小:

        private void changeFontSize(float fontsize)
        {
            int selStart = this.richTextBox.SelectionStart;
            int selLen = this.richTextBox.SelectionLength;
            int selEnd = selStart + selLen;

            if (richTextBox.SelectionFont != null)
            {
                Font oldFont = richTextBox.SelectionFont;
                richTextBox.SelectionFont = new Font(oldFont.Name, fontsize);
            }
            else
            {
                for (int i = selStart; i < selEnd; i++)
                {
                    this.richTextBox.Select(i, 1);
                    Font oldFont = richTextBox.SelectionFont;
                    if (oldFont != null)
                    {
                        richTextBox.SelectionFont = new Font(oldFont.Name, fontsize,);
                    }
                }
            }
            this.richTextBox.Select(selStart, selLen);
        }

好了,这样优化一下以后,我们可以让在选中文本中只有一种字体的情况下直接修改其字体大小,而不是傻了吧唧地在哪里循环。

很好,现在似乎已经接近完美了,不过这还不是最好的。如果选中的文本只有两种字体时,我们的算法效率是O(n)。而其实我们可以通过一些计算只修改两次字体即可。当然这些都是理想情况。不过我们仍然可以优化一下代码使之效率提升到O(LogN)。

那么是怎么一种算法呢?想想看,如果SelectionFont的返回值为null,那么我们就把选择区域分成两个部分,左边一半和右边一半。这样再分别判断左边和右边的SelectionFont是否为null,如果不是,那么修改字体大小,否则在分两半进行判断。没错,这就是二分法。下面我们用递归实现这种算法。

        private void changeFontSize(float fontsize)
        {
            int selStart = this.richTextBox.SelectionStart;
            int selLen = this.richTextBox.SelectionLength;
            int selEnd = selStart + selLen;

            changeHelp(selStart, selEnd, fontsize);

            this.richTextBox.Select(selStart, selLen);
        }

        private void changeHelp(int start, int end, float fontsize)
        {
            this.richTextBox.Select(start, end - start); ;
            if (richTextBox.SelectionFont != null)
            {
                Font oldFont = richTextBox.SelectionFont;
                richTextBox.SelectionFont = new Font(oldFont.Name, fontsize);
            }
            else
            {
                int mid = (start + end) / 2;

                changeHelp(start, mid, fontsize);
                changeHelp(mid, end, fontsize);
            }
        }

在changeHelp()方法中,我们有两条路可以走:

1.当选择区域的SelectionFont不为null时,修改选中区域的字体大小。

2.当选择区域的SelectionFont为null时,二分选中区域,然后调用changeHelp()方法对左右两部分进行修改。

好了,为了更加容易了解其中的计算过程,我们来看看这个算法的执行过程:

首先,假设我们的文字为“中文和汉字”其中,“中文和”两个字是A字体,“汉字”是B字体。

1. 调用_changeFontSize2(0, 5, size)方法:这时start为0,len为5,选中的区域为全部内容。当然,此时的SelectionFont肯定为null,进入了else后面的代码。

首先计算mid,值为2;然后调用_changeFontSize2(0, 2, size)方法。

2. 调用_changeFontSize2(0, 2, size)方法:这时选中的区域为“中文”两个字,其字体为A,所以可以修改字体大小。然后该函数返回。

3. 回到_changeFontSize2(0, 5, size)方法:此时调用_changeFontSize2(0 + 2, 5 - 2, size)方法。

4. 调用_changeFontSize2(2, 3, size)方法:此时选中的区域为“和汉字”3个字,不过这三个子有两个字体,所以还得拆分。计算mid,此时为1,接下来就是调用_changeFontSize2(2, 1, size)方法。

5.调用_changeFontSize2(2, 1, size)方法:此时选中的区域为“和”,字体为A,可以修改大小,然后返回。

6. 回到_changeFontSize2(2, 3, size)方法:此时调用changeFontSize2(2 + 1, 3 - 1, size)方法。

7. 调用_changeFontSize2(3, 2, size)方法:此时选中的区域为“汉字”,字体为B,可以修改字体大小,然后返回。

8.回到_changeFontSize2(2, 3, size)方法:函数调用结束,返回_changeFontSize2(0, 5, size)方法

9. 回到_changeFontSize2(0, 5, size)方法:这时整个选中区域的文字大小已经修改完毕,函数可以返回了。

可以看到,这个算法在修改文字的字体大小时仅仅进行了3次运算,比起直接使用循环要少了2次运算。而当我们选中更多的文字时,其算法的效率提升也会更高。

在此,我们用到了二分算法和递归,对于初学者来说,函数的递归调用[①]是一个比较难理解的概念,要理解它需要一些时间和一些实践。如果了解了函数调用的底层工作原理,那么对于递归的理解就更加方便了。对于二分法来说,想要了解更多的知识还是看一些讲解算法的书,那里面会讲一些二分法法的设计要点。

而从上面这个过程来看,最开始我们发现了问题,然后寻找了一个能避免错误出现的解决方法,再到一个能完全解决问题的方法,再到一个更优化的解决方法。这个过程的也许有些繁冗拖沓,但每当你前进一小步之后,可能背后所获得的知识会更多。如果你不前进到最后一步,那么你不会用到二分算法和函数的递归调用。

其实写程序可以是一个机械的过程:完成客户的既定要求。而有时候如果你把写代码当成是一种艺术创作,那么你的激情就会油然而生,而一些新的思想的火花会不知不觉的迸发出来。


[①] 关于递归调用,请参看第六章6.5节。