本鱼拟成立工作室承接项目开发/软件定制/云设施开发运维/办公设备技术支持等,如您有相关需求,欢迎来询 | ::博客文章推荐::

打造出一个可自动调整大小的文字显示控件(Label)

: DOT.NET 木魚 3170℃ 1评论

很少更新技术的博客,原因很简单,虽然不是从来不遇到问题但是遇到的问题很少会系统地总结出解决思路,也一直觉得没什么写出来的价值,大多是些小问题。今天遇到一个问题,解决很快不过几分钟的时间,但是还是写出来试试看吧。当然,还是老规矩,发到新手区,哎,没勇气发首页啊,估计也没发首页的必要吧。

Let's begin.

1.问题的提出
我们偶尔需要这样一个控件,就是只需要显示一段文本的label,但是呢,这个Label的文本我们是不知道长度的,但是我们却希望它能自动地随着Label的长度增加而增加它的显示区域。
我们用Label吧,好办,它能搞透明背景色的,这比较符合我的胃口。TextBox吗?不做手脚还透明不了~~哎,不符合要求啊不符合。

我们先来看看现在的label控件,能直接用吗?

1.都知道label有个AutoSize属性,并且默认是true的(不知道?那就给我面壁去)。这种模式下是会自动调整大小的,但是问题是,嗯,它的宽度也是自动调整的,意味着遇到换行才会开新行,如果这里有一行很长很长很长很长的文字,那会怎样?估计你猜对了,文字都overflow啦,看不到啦。至少到现在我还没找到怎么让其乖乖地自动换行的方法,你要是找到了千万别忘记告诉我啊,我会感激不尽的 🙂 
  image

2.关掉AutoSize以后又有一个问题了。文字是能自动换行,但是必须预置一个高度和宽度,在这个高度以外的文字都是显示不出来的。这回你要说啦,问题不就结了吗,我一开始就给它一个很高很高很高。。。。高到能够装下所有文字的高度不就万事大吉了吗?
貌似如此。但是我们的问题是不知道具体有多少文字呀,有可能只有一行,也可能有很多很多很多很多。。。。。行呢?比如我现在想要在一个FlowLayoutPanel这个流布局容器里面显示很多很多的Label来显示很多段文字,但是偏偏每段文字都不一样多的话,都预置好高度……似乎有点不现实吧,也是不可行的。(也许你会说谁会需要这么变态的功能啊!呵呵偏偏我就要用,咋办捏)。

问题就是供人解决的,我们来解决it吧。不好意思上面废话有点多,下面尽量少点。

2.从简单考虑?
从简单的考虑,我们会想要问题的解决简单一点,这就迫使我们会去考虑是否控件本身会提供了这样的功能?

在下面的考虑中,我们会使用相当多的联想和推测,这在程序开发中是个很重要的方面。我遇到相当多的人问的问题其实如果他们多联想一下和推测一下的话,是很容易自己解决的,也许这样会更有成就感一些。

首先,我们会考虑是否控件本身提供这样的功能了?从上面看显然没有啊,不然这篇文章还写啥写哦。
那我们就从如何实现它来考虑。这里的症结是,对应的文字所要求显示的区域是未知的,而控件的大小是固顶的,或自动调整的大小是不符合要求的。那我们先来选定一个前提状态,就是预置好Label的属性。AutoSize显然不能用了,那么就用固定大小的吧。这样还有一个优势,就是我们可以使用Label的Anchor属性固定好它的宽度,而只让它的高度自动变化。设置好属性的Label现在是这样的:(为了做例子这里放在了一个UserControl里面) 
image
注意下面还有很多很多很多的。。。垃圾文字哦,呵呵。先提前说一下,这里设置的大小是 300*117 。

现在来继续。我们可以在它显示的时候自动调整大小嘛。那现在的症结就是如何获得本身应该具有的最佳显示区域?
先从自身的属性上考虑?

关于显示区域的属性,Label原生的关于显示区域和尺寸的有下面这些属性:
DisplayRectangle , ClientRectangle , Size
看看上面的名字我觉得没用,下断点跟踪一下也确实没用,都是我眼看到的大小。

莫非非要我们自己用GDI+画一下才知道大小?
先浏览一下Label所有的属性,突然有个属性蹦进了视线: 
image
自从安装了VS2008 SP1以后提示全英文了。。我也很怨念的。

所以英文不看也罢。
但是看属性名字似乎有用哦。与此同时还有一个方法:GetPreferredSize() ,应该就是与这个属性相关联的函数。
从这个属性表面上看起来是获得最佳显示效果的尺寸,是这样吗?我们下断点看一下。 

image

相信你跟我也一样很 囧。因为这个尺寸一看就知道同AutoSize打开时自动调整的大小一样,就是文字不换行,很长很长很长很长。。。想知道效果?翻到这篇烂文的最开始去 :-(。

此路不通,呵呵,我们得换条方法。好像我们刚刚看到了一个。。。GetPreferredSize 函数?既然那个属性派不上,我们用这个玩意儿来试试看。
先让我们来看看声明。 
image
囧了,原来这个方法还要传递一个期望的尺寸。但是你猜如果我们把它当前的尺寸传递进去,会是什么效果?
我猜就是在给定宽度下最佳的新尺寸?你猜呢?试试看。 

image

开心了吧?看宽度很接近原始宽度,而高度却大了很多,很明显,这不就是最理想的嘛。
不过你可能会说,可是这个宽度咋是 295 呢?它的真正宽度不是300吗?这个需要牵涉到自动换行了,因为自动换行切换了文字,但是最后的宽度不够显示一个字符的,这就留下一段空白了,这几个像素就是留空,不用理它,最后显示的时候只要按照原始宽度300来就OK了。

那现在我们写下这段代码?

        public TestControl()
				{
						InitializeComponent();

						System.Drawing.Size s = label1.GetPreferredSize(label1.Size);
						label1.Size = s;
				}


F5一下~恩,结果很好。 

image

3.我们考虑封装一下?
如果只是上面的一个控件那还好办,可是如果我们现在想用很多很多很多这样的控件呢?或我们需要动态加载呢?这就涉及到易用性的问题了。我们要不再来封装一下吧。
我们来建立一个自定义的控件吧,把 Label 扩展下,就叫……LabelEx吧。

   1:      internal class LabelEx : System.Windows.Forms.Label
   2:      {
   3:          public LabelEx()
   4:              : base()
   5:          {
   6:              //关闭AutoSize
   7:              this.AutoSize = false;
   8:   
   9:              //捕捉事件
  10:              this.TextChanged += new EventHandler(LabelEx_TextChanged);
  11:          }
  12:   
  13:          /// <summary>
  14:          /// 重写AutoSize属性,防止AutoSize再被打开
  15:          /// </summary>
  16:          public override bool AutoSize
  17:          {
  18:              get
  19:              {
  20:                  return false;
  21:              }
  22:          }
  23:   
  24:          void LabelEx_TextChanged(object sender, EventArgs e)
  25:          {
  26:              //文字变化了,那就改变一下当前的大小
  27:              System.Drawing.Size ps = GetPreferredSize(this.Size);
  28:   
  29:              //这里构造一个新的Size对象,目的是使用原始的宽度。原因嘛,见上面
  30:              this.Size = new System.Drawing.Size(this.Width, ps.Height);
  31:          }
  32:      }

现在编译一下,然后拖一个到界面上,然后在代码里面手动绑定它的Text属性(不可以在设计器里面修改,这样你会发现你的设计器里面的控件尺寸是始终自动变化的)。
运行看看效果? 

image

(加了一个边框是为了看效果)

设置代码如下:

   1:          public TestControl()
   2:          {
   3:              InitializeComponent();
   4:   
   5:              labelEx1.Text = "label1label1label1label1label1label1label1llabel1label1label1label1n" +
   6:  "label1label1n" +
   7:  "label1label1n" +
   8:  "label1label1n" +
   9:  "label1label1label1label1label1label1label1label1label1label1label1labeel1label1label1label1label1label1n" +
  10:  "label1label1n" +
  11:  "label1label1n" +
  12:  "label1label1n" +
  13:  "label1label1label1label1label1label1label1label1label1label1label1lbel1label1label1label1label1label1label1n" +
  14:  "label1label1n" +
  15:  "label1label1n" +
  16:  "label1label1n";
  17:          }


显示如下: 
image

貌似很符合要求?活活。

4.我们扩展下?
有时候我们会考虑在一个子控件里面封装这么个东东,比如加点花边之类的。可是里面的label纵然会自动调整大小可是容器不行,那可不好,因为溢出的东东又会隐藏了,就像这样的: 
image

Bingo!

当然是有解决方法的,因为有个父控件呗!
改!

重写 LabelEx_TextChanged 函数如下:

   1:          void LabelEx_TextChanged(object sender, EventArgs e)
   2:          {
   3:              //记录下当前的高度
   4:              int oh = this.Height;
   5:   
   6:              //文字变化了,那就改变一下当前的大小
   7:              System.Drawing.Size ps = GetPreferredSize(this.Size);
   8:   
   9:              //这里构造一个新的Size对象,目的是使用原始的宽度。原因嘛,见上面
  10:              this.Size = new System.Drawing.Size(this.Width, ps.Height);
  11:   
  12:              if (this.Parent != null && AutoResizeParent)
  13:              {
  14:                  //调整容器大小
  15:                  this.Parent.Size = new System.Drawing.Size(this.Parent.Width, ps.Height - oh + this.Parent.Height);
  16:              }
  17:          }


添加一个功能开关的属性

   1:          /// <summary>
   2:          /// 是否自动调整父控件
   3:          /// </summary>
   4:          public bool AutoResizeParent { get; set; }

 image

哦也?哦也。

可是我隐隐约约好像还有问题啊。。。比如原本好像显示得好好的: 
image
可是我把窗体缩小(利用Anchor让LabelEx也缩小)后惊喜地发现…… 

image

🙁

好吧,继续改写,好了~ 
image

最终代码如下:

   1:      internal class LabelEx : System.Windows.Forms.Label
   2:      {
   3:          public LabelEx()
   4:              : base()
   5:          {
   6:              //关闭AutoSize
   7:              this.AutoSize = false;
   8:   
   9:              //捕捉事件
  10:              this.TextChanged += new EventHandler(LabelEx_TextChanged);
  11:              this.SizeChanged += new EventHandler(LabelEx_SizeChanged);
  12:          }
  13:   
  14:          //记录原始的宽度
  15:          int _oldw;
  16:   
  17:          void LabelEx_SizeChanged(object sender, EventArgs e)
  18:          {
  19:              //如果宽度没变,那就返回
  20:              if (this.Width == _oldw) return;
  21:   
  22:              //重新计算高度
  23:              LabelEx_TextChanged(null, null);
  24:   
  25:              //记录
  26:              _oldw = this.Width;
  27:          }
  28:   
  29:          /// <summary>
  30:          /// 重写AutoSize属性,防止AutoSize再被打开
  31:          /// </summary>
  32:          public override bool AutoSize
  33:          {
  34:              get
  35:              {
  36:                  return false;
  37:              }
  38:          }
  39:   
  40:          /// <summary>
  41:          /// 是否自动调整父控件
  42:          /// </summary>
  43:          public bool AutoResizeParent { get; set; }
  44:   
  45:          void LabelEx_TextChanged(object sender, EventArgs e)
  46:          {
  47:              //记录下当前的高度
  48:              int oh = this.Height;
  49:   
  50:              //文字变化了,那就改变一下当前的大小
  51:              System.Drawing.Size ps = GetPreferredSize(this.Size);
  52:   
  53:              //这里构造一个新的Size对象,目的是使用原始的宽度。原因嘛,见上面
  54:              this.Size = new System.Drawing.Size(this.Width, ps.Height);
  55:   
  56:              if (this.Parent != null && AutoResizeParent)
  57:              {
  58:                  //调整容器大小
  59:                  this.Parent.Size = new System.Drawing.Size(this.Parent.Width, ps.Height - oh + this.Parent.Height);
  60:              }
  61:          }
  62:      }

5.用处?
我们上面写这一大票还是不好使啊!比如这个labelex控件的容器(比如上面的GroupBox)的大小溢出了,还是要隐藏的啊。
呵呵。不过我们早就说过了,这个东东最主要是用在流式布局的(因为溢出会自动滚动),比如下面的代码:

   1:          public Form1()
   2:          {
   3:              InitializeComponent();
   4:   
   5:   
   6:              LoadFlowControls();
   7:          }
   8:   
   9:          void LoadFlowControls()
  10:          {
  11:              //允许自动滚动
  12:              flowLayoutPanel1.AutoScroll = true;
  13:   
  14:              string text = "这里是第 {0} 段第 {0} 行文字,测试测试啦!n";
  15:   
  16:              System.Text.StringBuilder sb = new StringBuilder();
  17:   
  18:              for (int i = 1; i < 10; i++)
  19:              {
  20:                  sb.AppendFormat(text, i.ToString());
  21:   
  22:                  //创建LabelEx控件
  23:                  LabelEx lab = new LabelEx()
  24:                  {
  25:                      Text = sb.ToString(),
  26:                      Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
  27:                      Location = new Point(0, 0),
  28:                      Left = 0,
  29:                      Size = new Size(flowLayoutPanel1.ClientSize.Width - 30, 10),
  30:                      //加边框以便观察
  31:                      BorderStyle = BorderStyle.FixedSingle
  32:                  };
  33:                  //添加到容器里面
  34:                  flowLayoutPanel1.Controls.Add(lab);
  35:              }
  36:          }

显示效果如下: 

image

嘿。现在能猜出来我要做啥了不?

是一个可以显示论坛帖子文字内容的Label~~~哈哈

 

PS,本文在WLW中发布的,再编辑一次后所有的CSS样式全部丢失了,所以代码高亮都无效的,咳。

 

喜欢 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. 撸过

    那个同学2015-02-15 11:44 回复