C# Winform实现自定义漂亮的通知效果
作者:代码迷途??????? 发布时间:2021-08-10 08:15:29
前言
本文主要介绍其具体的实现思路(视频仅有代码输入,并无过程介绍等),同时,在原本实现的基础上,进行了多处修改和优化,具体参见下面的内容。
优化调整
下面是对源代码的修改、优化和调整:
修改 lblMsg(Label) 的 AutoSize 为false,尽可能多占通知窗体区域,Anchor跟随窗体变换,文字左侧垂直居中,用于显示可能更多的消息.
设定action、timer1默认值,Name、Opacity、StartPosition(Manual)在构造函数中指定,这样就不用每次创建通知窗体时进行赋值了。
ShowInTaskbar = false;
通知窗体不在任务栏显示。将原有代码中定时器时间间隔调整到100毫秒,原设置为1,时间太短人眼看不出区别,白白浪费计算。
ShowNotice()
改为静态方法,直接通过Form_Alert.ShowNotice(msg, msgType);
调用显示窗体,不用new创建对象再调用。AlertFormNum
静态属性设置最多显示的通知数量,默认尽可能多的占满垂直屏幕,手动设置数量不能低于1或超出屏幕。ShowTime
静态属性设置完全显示后通知的显示时间,单位毫秒;也可以扩展渐变显示和消失的时间。MoveEntry
静态属性设置消息框是否水平移动进入,默认true。通过设置初始的消息框位置,即可实现水平移动进入。实现消息框占满后,新的消息框替换最近消失的通知的功能。原实现中最多只能显示10个通知框,当再多时不会显示(丢失掉),只有腾出位置(通知消失后)才能显示新的,现在已经优化为超出的通知框会替换掉旧通知,不会丢失。
下图为示例,后半段显示的内容是设置最多显示5个消息框时,发生替换的效果;
// 设置通知的数量
Form_Alert.AlertFormNum = 5;
Form_Alert.MoveEntry = false;// 不水平移动进入
调整下图标位置,垂直方向居中一些
水平移动进入的效果(默认):
添加显示时指定消息字体的参数,有需要可直接修改显示文字的字体。
/// <summary>
/// 设置完x、y之后执行初始化启动。设置位置、消息类型、显示、倒计时
/// </summary>
/// <param name="msg"></param>
/// <param name="msgType"></param>
/// <param name="msgFont">字体,默认不指定即可</param>
private void InitStart(string msg, MsgType msgType, Font msgFont = null)
{
// ...
}
调用并显示自定义通知
新建项目NotificationCustom
,完成通知框的调用显示
Form_Alert.ShowNotice("这是一条成功的消息", MsgType.Success);
Form_Alert.ShowNotice("警告!警告的消息", MsgType.Warning);
Form_Alert.ShowNotice("发生了错误,禁止!", MsgType.Error);
Form_Alert.ShowNotice("一条普通的信息记录", MsgType.Info);
或者显示时指定字体(下面为随机字体)
Form_Alert.ShowNotice("这是一条成功的消息", MsgType.Success, new Font(FontFamily.Families[random.Next(0, FontFamily.Families.Length)], (float)(10.0+10.0*random.NextDouble())));
主要实现过程
创建一个无边框窗体Form_Alert,添加Label(
lblMsg
)显示通知消息,添加一个表示关闭的图片(PictureBox)。设置窗体
StartPosition = FormStartPosition.Manual;
,后面用于设置其初始位置为指定的屏幕右下角通过不同的背景颜色、不同的图片(icon,PictureBox)代表不同的消息类型(
MsgType
)定时器中通过定时时间完成消息窗的显示(透明度变化)、显示一定时间、关闭(逐渐透明)整个流程:定义消息窗体不同的操作(
NotificationFormAction
),start表示开始显示,显示窗体并在定时器中处理透明、移入的显示过程,完全显示后改变操作状态为wait;设置消息窗体显示等待的时间,操作状态变为close,定时时间之后再次执行定时器进入close处理;close过程中定时器执行变得透明、移出,完全透明后关闭定时器、关闭窗体。点击关闭按钮图标,窗体状态变为close,定时时间改为close的间隔100
每次定时器执行函数的结尾记录下次执行的时间,用于判断当两个窗体的状态相同时,剩余执行时间为多少,判断哪个窗体最先消失,用于完成后面的消息通知太多时,新旧消息框的替换【不严谨,尤其在逐渐的显示和关闭过程中,有着多次的定时器循环,如果想要完全严格,可以考虑计算消息窗体最终消失的时间(消息框的状态,循环执行的剩余次数,每次的间隔时间综合计算)】
ShowNotice()
静态方法显示消息框,直接传递要显示的消息和消息类型即可,分为Success
,Warning
,Error
,Info
四类,通过指定的 AlertFormNum 消息框数量(或默认数量),循环依次显示消息框,并启动定时器处理消息框的窗体状态:渐变显示(透明度)、显示一定时间(ShowTime)、渐变消失。循环中通过Application.OpenForms[fname]
获取通知框窗体,如果没有获取到则创建新窗体,并执行显示,结束整个显示处理;在循环中记录已有窗体中最先消失的窗体;如果全部循环完,则说明所有数量的通知框都存在,则完成对最先消失的窗体的替换并显示新的消息窗体。
代码实现
修改后全部代码不到200行,如下,主要部分已经进行注释:
namespace CustomAlertBoxDemo
{
public enum NotificationFormAction
{
start,
wait,
close
}
public enum MsgType
{
Success,
Warning,
Error,
Info
}
public partial class Form_Alert : Form
{
/// <summary>
/// 通知窗体的数量,默认为垂直屏幕几乎占满的数量
/// </summary>
private static int alertFormNum = Screen.PrimaryScreen.WorkingArea.Height / (75 + 5); // 75为窗体高度,如果调整窗体高度,记得修改此处
/// <summary>
/// 通知窗体的数量,默认为垂直屏幕几乎占满的数量,手动修改的数量不能超出屏幕和低于1,否则设置无效
/// </summary>
public static int AlertFormNum
{
get => alertFormNum;
set
{
if (value <= Screen.PrimaryScreen.WorkingArea.Height / (75 + 5) && value > 0)
{
alertFormNum = value;
}
}
}
/// <summary>
/// 自定义通知的显示时间,单位为毫秒,默认为3分钟,之后开始消失。可根据需要修改
/// </summary>
public static int ShowTime { get; set; } = 3000;
/// <summary>
/// 是否移动进入,默认true
/// </summary>
public static bool MoveEntry { get; set; } = true;
/// <summary>
/// 创建通知窗体
/// </summary>
/// <param name="name">窗体名称,必须指定</param>
public Form_Alert(string name)
{
InitializeComponent();
Name = name;
this.Opacity = 0.0;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
}
private NotificationFormAction action = NotificationFormAction.start;
/// <summary>
/// 当前消息框的标准位置
/// </summary>
private int x, y;
private void timer1_Tick(object sender, EventArgs e)
{
switch (this.action)
{
case NotificationFormAction.wait:
timer1.Interval = ShowTime;
action = NotificationFormAction.close;
break;
case NotificationFormAction.start:
this.timer1.Interval = 100;
this.Opacity += 0.1;
if (this.x < this.Location.X)
{
this.Left-=20; // 移动快点
}
else
{
if (this.Opacity == 1.0)
{
action = NotificationFormAction.wait;
}
}
break;
case NotificationFormAction.close:
timer1.Interval = 100;
this.Opacity -= 0.1;
this.Left -= 20;
if (base.Opacity == 0.0)
{
timer1.Stop();
base.Close();
}
break;
}
// tag记录下次执行的时间,用于后续的替换
timer1.Tag = DateTime.Now.AddMilliseconds(timer1.Interval);
}
private void pictureBox2_Click(object sender, EventArgs e)
{
timer1.Interval = 100;
action = NotificationFormAction.close;
}
/// <summary>
/// 设置完x、y之后执行初始化启动。设置位置、消息类型、显示、倒计时
/// </summary>
/// <param name="msg"></param>
/// <param name="msgType"></param>
private void InitStart(string msg, MsgType msgType)
{
//this.Location = new Point(frm.x, frm.y);
this.Location = new Point(x + (MoveEntry?Width / 2:0), y);
switch (msgType)
{
case MsgType.Success:
pictureBox1.Image = Resources.success;
BackColor = Color.SeaGreen;
break;
case MsgType.Error:
pictureBox1.Image = Resources.error;
BackColor = Color.DarkRed;
break;
case MsgType.Info:
pictureBox1.Image = Resources.info;
BackColor = Color.RoyalBlue;
break;
case MsgType.Warning:
pictureBox1.Image = Resources.warning;
BackColor = Color.DarkOrange;
break;
}
lblMsg.Text = msg;
Show();
timer1.Start();
}
public static void ShowNotice(string msg, MsgType msgType)
{
Form_Alert willDisappearFrm = null;
for (int i = 1; i < alertFormNum+1; i++)
{
string fname = "alert" + i.ToString();
Form_Alert frm = (Form_Alert)Application.OpenForms[fname];
if (frm == null)
{
frm = new Form_Alert(fname);
frm.x = Screen.PrimaryScreen.WorkingArea.Width - frm.Width - 5;
frm.y = Screen.PrimaryScreen.WorkingArea.Height - frm.Height * i - 5 * i;
// 设置完x、y之后执行初始化启动
frm.InitStart(msg, msgType);
return;
}
else
{
if (willDisappearFrm == null)
{
willDisappearFrm = frm;
}
else
{
if (willDisappearFrm.action < frm.action)
{
willDisappearFrm = frm;
}
else if (willDisappearFrm.action == frm.action)
{
// 不考虑一次没执行的情况
if (willDisappearFrm.timer1.Tag!=null&& frm.timer1.Tag != null)
{
if (willDisappearFrm.timer1.Tag == null)
{
willDisappearFrm = frm;
}
else if(frm.timer1.Tag != null)
{
if ((DateTime)willDisappearFrm.timer1.Tag > (DateTime)frm.timer1.Tag)
{
willDisappearFrm = frm;
}
}
}
}
}
}
}
// 当前最早要消失的窗体willDisappearFrm被替换
var newfrm = new Form_Alert(willDisappearFrm.Name);
newfrm.x = Screen.PrimaryScreen.WorkingArea.Width - newfrm.Width - 5;
newfrm.y = willDisappearFrm.Location.Y;
// 必须立即替换name
var totalNum = 0;
foreach (Form form in Application.OpenForms)
{
if (form is Form_Alert)
{
totalNum += 1;
}
}
willDisappearFrm.Name = $"Form_Alert{totalNum + 1}";
willDisappearFrm.pictureBox2_Click(null, null);
// 设置完x、y之后执行初始化启动
newfrm.InitStart(msg, msgType);
}
}
}
来源:https://juejin.cn/post/7135284656752033828


猜你喜欢
- Android插件开启对新Api的支持这一天小王导入了一个库,上线之后直接崩了一大片? 找到其中的问题:什么鬼哦?安卓8.0一下无法使用?
- Java 存储模型和共享对象详解很多程序员对一个共享变量初始化要注意可见性和安全发布(安全地构建一个对象,并其他线程能正确访问)等问题不是很
- hi 大家好,今日,天气剧变,非常冷,不想出门,于是给大家写了篇文章,关于android final关键字及final,finally,fi
- 开发环境:IntelliJ IDEA 2019.2.2Spring Boot版本:2.1.8一、发布REST服务1、IDEA新建一个名称为r
- 手机一般有两种类型的输入设备。一种是键盘类型的输入设备,通常它包含电源键和音量下键。另一种是触摸类型的输入设备,触摸屏就属于这种类型。键盘类
- 前言本次示例代码的文件结构如下图所示。1. 导入依赖坐标在 order-service 的 pom.xml 文件中导入 Feign 的依赖坐
- 一.服务器端获取Session对象依赖于客户端携带的Cookie中的JSESSIONID数据。如果用户把浏览器的隐私级别调到最高,这时浏览器
- 一条SQL使用两个foreach的问题未修改前的 SQL 语句<select id="findQuestionType_3_
- 面向过程和面向对象的区别面向过程:当事件比较简单的时候,利用面向过程,注重的是事件的具体步骤和过程,注重的是过程中的具体行为,以函数为最小单
- WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对
- 问题提出:一般在生产环境上,日志的级别是INFO以上,但有时候程序出现问题(如SQL报错),少量日志不能尽快定位问题,这时候可以动态修改日志
- MessageFormat本身与语言环境无关,而与用户提供给MessageFormat的模式和用于已插入参数的子格式模式有关,以生成适用于不
- 本文实例讲述了Android开发之绘制平面上的多边形功能。分享给大家供大家参考,具体如下:计算机里的3D图形其实是由很多个平面组合而成的。所
- ShardingSphere的路由引擎类型本篇文章源码基于4.0.1版本上篇文章我们了解到了ShardingSphere在路由流程过程中,根
- 1、首先创建一个测试实体类Person,并携带如上注解,其注解的作用描述在messagepackage com.clickpaas.pojo
- 一、问题说明偶然换了下spring boot的版本号,结果idea一直标红,报该父依赖一直找不到。但是当我查看引入的依赖时,版本号已经变成2
- 网上搜集了java的学习思维导图,分享给大家。01.Java程序设计(基础)02.Java程序设计(专题)03.客户端网页编程04.Java
- 最近项目中用到了service进行计时,在连接USB的情况下一切正常,但是拔掉USB后发现,手机进入休眠后service停止了工作。最后通过
- 目录1.问题引出2.解决办法3.另外一种自定义序列化机制(介绍Externalizable)1.问题引出在某些情况下,我们可能不想对于一个对
- 1、问题起因最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享