Winform控件优化之TabControl控件的美化和功能扩展

Winform控件优化之TabControl控件的美化和功能扩展在基本的TabControl控件使用和功能之上,可以尝试对其进行美化和功能扩展,比如动态删除或添加tab、绘制图标按钮及鼠标hover时的背景变化、Tab从右向左布局的优化处理等。最重要…

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

在基本的TabControl控件使用和功能之上,可以尝试对其进行美化和功能扩展,比如动态删除或添加tab、绘制图标按钮及鼠标hover时的背景变化、Tab从右向左布局的优化处理等。最重要的是推荐参考花木兰控件库中对TabControl美化,现代UI效果的tab,绝对值得一看或者使用。

实现动态增加或删除tab的功能(添加和关闭按钮)

添加、关闭按钮图标

通过ImageList或Resources添加“加号”和“关闭”图片作为全局变量,也可以指定这两个图片的路径为全局变量。

此处使用imageList。

Winform控件优化之TabControl控件的美化和功能扩展

设置DrawMode为OwnerDrawFixed

tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;

DrawItem中绘制添加、关闭按钮

tabControl1.DrawItem += TabControl1_DrawItem2;

//.....
// 绘制add和close按钮
private void TabControl1_DrawItem2(object sender, DrawItemEventArgs e)
{
   var tabPage = tabControl1.TabPages[e.Index];
   var tabRect = tabControl1.GetTabRect(e.Index);
// tabRect.Inflate(0, -2); // 似乎未起作用
   //e.DrawBackground(); // 背景
   if (e.Index == tabControl1.TabCount - 1) // 最后一个TabPage
   {
       var addImage = imageList1.Images["add"]; // 也可以从路径获取new Bitmap(imagePath);
       e.Graphics.DrawImage(addImage,
           tabRect.Left + (tabRect.Width - addImage.Width) / 2,
           tabRect.Top + (tabRect.Height - addImage.Height) / 2);
   }
   else // 其他TabPages绘制关闭
   {
       var closeImage = imageList1.Images["close"];
       e.Graphics.DrawImage(closeImage,
           (tabRect.Right - closeImage.Width - 2),
           tabRect.Top + (tabRect.Height - closeImage.Height) / 2);

       TextRenderer.DrawText(e.Graphics, tabPage.Text, tabPage.Font,
           new Rectangle(tabRect.X, tabRect.Y, tabRect.Width-closeImage.Width, tabRect.Height), tabPage.ForeColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
   }
}

设置最后一个“添加”按钮的tab尽可能短

通过SendMessage使最后一个“添加”按钮的tab比较短。

实际测试要想此方法生效,TabControl.SizeMode 不能为 Fixed

// 创建句柄时触发。通过发送消息SendMessage使最后一个添加tab尽可能短。SizeMode 不能为 Fixed
tabControl1.HandleCreated += TabControl1_HandleCreated;

//.....
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
private const int TCM_SETMINTABWIDTH = 0x1300 + 49;
private void TabControl1_HandleCreated(object sender, EventArgs e)
{
   SendMessage(tabControl1.Handle, TCM_SETMINTABWIDTH, IntPtr.Zero, (IntPtr)16);
}

Winform控件优化之TabControl控件的美化和功能扩展

可以看到,在使用了非FixedSizeMode后,由于tab的宽度设置无效,导致默认的宽度仅可以显示文字。在重新绘制时,原本的文字范围内添加了close图片,占用了16px的范围(close图片的大小为16*16),剩余的范围无法一行放下文字,导致了tab文字的换行。

比较好的解决办法就是添加一个padding.X的值,最好为16px,正好可以放下绘制close图片。

效果如下:

Winform控件优化之TabControl控件的美化和功能扩展

实际可以自行调整padding.X的值,未必需要指定16,比如13、12都可以正常一行放下文字。

Winform控件优化之TabControl控件的美化和功能扩展

设置鼠标点击关闭和添加按钮时的处理

处理最后一个tab(即”添加”按钮的tab)点击时,动态添加tabpage;点击tab的关闭按钮时关闭移除当前tab。

在MouseDown事件中,获取鼠标按下时的位置,并依次判断是否点击在close按钮上或最后一个“添加”按钮的tab上,依据判断结果执行添加和关闭操作。

tabControl1.MouseDown += TabControl1_MouseDown;

//.....
private void TabControl1_MouseDown(object sender, MouseEventArgs e)
{
   // 依次循环判断,鼠标点击位置是否位于close图片范围内;或是否位于“添加”按钮tab内
   for (var i = 0; i < tabControl1.TabPages.Count; i++)
   {
       var tabRect = tabControl1.GetTabRect(i);
       if (i == tabControl1.TabPages.Count - 1) // 组后一个 add 按钮
       {
           if (tabRect.Contains(e.Location))
           {
               CreateTabPage();
           }
       }
       else
       {
           var closeImage = imageList1.Images["close"];
           var imageRect = new Rectangle(
               tabRect.Right - closeImage.Width - 2,
               tabRect.Top + (tabRect.Height - closeImage.Height) / 2,
               closeImage.Width,
               closeImage.Height);
           if (imageRect.Contains(e.Location))
           {
               tabControl1.TabPages.RemoveAt(i);
               break;
           }
       }
   }
}

创建tabpage的方法CreateTabPage(以实际情况实现)

private void CreateTabPage()
{
   //insert会导致DrawItem中异常(索引错误)
   //tabControl1.TabPages.Insert(tabControl1.TabPages.Count - 1,"新选项卡"+(tabControl1.TabPages.Count - 1));
   tabControl1.TabPages.Add("新选项卡" + (tabControl1.TabPages.Count - 1));
   var addPage = tabControl1.TabPages["add"];
   tabControl1.TabPages.Remove(addPage);
   tabControl1.TabPages.Add(addPage);
}

鼠标位于关闭按钮上方时背景变化效果

依据MouseDown处理中判断鼠标位置的方法,通过在MouseMove判断鼠标位置,可以绘制鼠标位于关闭按钮上方时,“关闭”背景相应变化(比如变灰)。

tabControl1.MouseMove += TabControl1_MouseMove;

//.......
/// <summary>
/// 鼠标Hover关闭按钮效果
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TabControl1_MouseMove(object sender, MouseEventArgs e)
{
   // 依次循环判断,鼠标点击位置是否位于close图片范围内
   for (var i = 0; i < tabControl1.TabPages.Count - 1; i++)
   {
       var tabPage = tabControl1.TabPages[i];
       var tabRect = tabControl1.GetTabRect(i);
       //tabRect.Inflate(0, -2); // 似乎未起作用
       var closeImage = imageList1.Images["close"];
       var imageRect = new Rectangle(
           tabRect.Right - closeImage.Width - 2,
           tabRect.Top + (tabRect.Height - closeImage.Height) / 2,
           closeImage.Width,
           closeImage.Height);
       if (imageRect.Contains(e.Location))
       {
           if (tabPage.Tag?.ToString() != "1")
           {
               using (var g = tabControl1.CreateGraphics())
               {
                   g.FillRectangle(new SolidBrush(Color.FromArgb(100, 100, 100, 45)), imageRect);
                   tabPage.Tag = "1";//表示已有透明灰色背景
               }
           }
           break;
       }
       else
       {
           if (tabPage.Tag?.ToString() == "1")//清除已有的灰色背景
           {
               using (var g = tabControl1.CreateGraphics())
               {
                   g.FillRectangle(new SolidBrush(tabPage.BackColor), imageRect);
                   g.DrawImage(closeImage, imageRect);
                   tabPage.Tag = null;
               }
           }
       }
   }
}

效果如下,分别查看点击添加、鼠标悬停时、点击关闭按钮的效果:

Winform控件优化之TabControl控件的美化和功能扩展

设置RightToLeftLayout = trueRightToLeft = true时tab选项卡的绘制优化

上面的实现,如果设置属性RightToLeftLayout = trueRightToLeft = true时,将会出现选项卡文字和关闭图标分离混乱的问题。

Winform控件优化之TabControl控件的美化和功能扩展

因此需要优化RightToLeft模式下的tab绘制。

RightToLeft的坐标转换(Rectangle的坐标)可以从原始坐标通过下面的函数,从容器中矩形的坐标转换为RTL坐标:

public static Rectangle GetRTLCoordinates(Rectangle container, Rectangle drawRectangle)
{
   return new Rectangle(
       container.Right - drawRectangle.Width - drawRectangle.X,
       drawRectangle.Y,
       drawRectangle.Width,
       drawRectangle.Height);
}

因此,扩展下获取TabRect的方法如下:

/// <summary>
/// 绘制tab时需要考虑RTF模式
/// </summary>
/// <param name="tabCtl"></param>
/// <param name="idx"></param>
/// <returns></returns>
public static Rectangle GetTabRect(TabControl tabCtl, int idx)
{
   var tabRect = tabCtl.GetTabRect(idx);
   if (tabCtl.RightToLeftLayout && tabCtl.RightToLeft == RightToLeft.Yes) // RTL
   {
       tabRect = GetRTLCoordinates(tabCtl.ClientRectangle, tabRect);
   }
   return tabRect;
}

通过GetTabRect获取正确的tab的矩形区域。

DrawItem事件方法对应修改为如下:

private void TabControl1_DrawItem2(object sender, DrawItemEventArgs e)
{
   var tabPage = tabControl1.TabPages[e.Index];
   var tabRect = GetTabRect(tabControl1,e.Index);

   //tabRect.Inflate(0, -2);
   //e.DrawBackground(); // 背景
   if (e.Index == tabControl1.TabCount - 1) // 最后一个TabPage
   {
       var addImage = imageList1.Images["add"]; // 也可以从路径获取new Bitmap(imagePath);
       e.Graphics.DrawImage(addImage,
           tabRect.Left + (tabRect.Width - addImage.Width) / 2,
           tabRect.Top + (tabRect.Height - addImage.Height) / 2);
   }
   else // 其他TabPages绘制关闭
   {
       using (var sf = new StringFormat(StringFormat.GenericDefault))
       {
           sf.Alignment = StringAlignment.Center;
           sf.LineAlignment = StringAlignment.Center;

           if (tabControl1.RightToLeft == RightToLeft.Yes && tabControl1.RightToLeftLayout == true) // RTL模式
           {
               sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
           }

           var closeImage = imageList1.Images["close"];
           var imgRect = new Rectangle(//tabRect.Right - closeImage.Width - 2,
               tabRect.Right - closeImage.Width - (tabControl1.RightToLeftLayout && tabControl1.RightToLeft == RightToLeft.Yes ? 4 : 2),
               tabRect.Top + (tabRect.Height - closeImage.Height) / 2, closeImage.Width, closeImage.Height);
           e.Graphics.DrawImage(closeImage, imgRect.Location);

           var textRect = new Rectangle(tabRect.X, tabRect.Y, tabRect.Width - closeImage.Width, tabRect.Height);
           e.Graphics.DrawString(tabPage.Text, tabPage.Font, new SolidBrush(tabPage.ForeColor), textRect, sf);
       }                
   }
}

MouseMove和MouseDown事件处理中,tabRect获取方式也改为此方法:var tabRect = GetTabRect(tabControl1, i);

同时注意,判断鼠标位置时,e.Location在RTF下也要进行转换,才能获取正确的位置:

var mousePos = e.Location;
if (tabControl1.RightToLeftLayout && tabControl1.RightToLeft == RightToLeft.Yes) // RTL调整鼠标位置
{
  mousePos.X = tabControl1.Right - mousePos.X;
}

Winform控件优化之TabControl控件的美化和功能扩展

此部分主要参考自从右到左TabControl的TabPage的关闭按钮C#

TabControl的[终极]美化扩展

关于TabControl的[终极]美化扩展,可以参考花木兰控件库的实现,该控件库通过重写 OnPaintOnMouseClick 方法,实现全部的绘制优化和美化。

Winform控件优化之TabControl控件的美化和功能扩展

源码参考TabControlExt.cs

文章介绍 TabControl美化扩展———-WinForm控件开发系列

参考

以上的优化没有进行继承TabControl控件,重写重绘方法,如果想要继承优化的,重写OnMouseClick时注意判断鼠标左右中键的处理、点击时选中tab的处理的,可简要参考下c#重写TabControl控件实现关闭按钮

另,TabControl控件的美化介绍的优化,源代码实在有点长,有兴趣可以了解下

今天的文章Winform控件优化之TabControl控件的美化和功能扩展分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21607.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注