< 150 ? 150 : expandHeight; } set { expandHeight = value; } } #region 属性 /// /// 是否启用动画效果 /// /// 此处还弄该属性是为了保证窗体类的独立性 private bool UseAnimate { get; set; } /// /// 是否启用声音反馈 /// /// 此处还弄该属性是为了保证窗体类的独立性 private bool UseSound { get; set; } /// /// 消息按钮 /// private MessageBoxButtons MessageButtons { get; set; } /// /// 消息图标 /// private MessageBoxIcon MessageIcon { get; set; } /// /// 默认按钮 /// private MessageBoxDefaultButton DefaultButton { get; set; } #endregion /// /// 创建消息窗体 /// private MessageForm(bool enableAnimate) { this.UseAnimate = enableAnimate;//须尽早设置,要供展开按钮初始化用 InitializeComponent(); this.StartPosition = Form.ActiveForm == null ? FormStartPosition.CenterScreen : FormStartPosition.CenterParent; this.Font = SystemFonts.MessageBoxFont; //注册事件 this.button1.Click += button_Click; this.button2.Click += button_Click; this.button3.Click += button_Click; this.plAttachZone.Resize += plAttachZone_Resize; } /// /// 创建消息窗体 /// public MessageForm(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, string attachMessage, bool enableAnimate, bool enableSound) : this(enableAnimate) { this.lbMsg.Text = message; this.Text = caption; this.txbAttach.Text = attachMessage; this.MessageButtons = buttons; this.MessageIcon = icon; this.DefaultButton = defaultButton; this.UseSound = enableSound; } #region 重写基类方法 protected override void OnLoad(EventArgs e) { //须在计算各种尺寸前搞掂 ProcessIcon(); ProcessButtons(); this.MinimumSize = SizeFromClientSize(new Size(GetPanelButtonMinWidth(), GetClientMinHeight())); //参数意义定为客户区最大大小,所以需刨掉非客户区高度后传入 this.ClientSize = this.GetPreferredSize(new Size(MaxClientWidth, Screen.PrimaryScreen.WorkingArea.Height - (this.Height - this.ClientSize.Height))); base.OnLoad(e); } protected override void OnShown(EventArgs e) { //设置默认按钮焦点。须在OnShown中设置按钮焦点才有用 Button dfBtn; if ((dfBtn = this.AcceptButton as Button) != null) { dfBtn.Focus(); } //播放消息提示音 if (this.UseSound) { PlaySystemSound(this.messageSound); } base.OnShown(e); } //重写窗体参数 protected override CreateParams CreateParams { get { CreateParams prms = base.CreateParams; if ((Convert.ToInt32(this.MessageButtons) & 1) == 0) //没有Cancel按钮时屏蔽关闭按钮,刚好在偶数项 { prms.ClassStyle |= 0x200; } return prms; } } /// /// 计算合适的窗口尺寸 /// /// 该参数此处定义为客户区可设置的最大尺寸 public override Size GetPreferredSize(Size proposedSize) { int reservedHeight = plButtonsZone.Height + Padding.Bottom; Size size = lbMsg.GetPreferredSize(new Size(proposedSize.Width, proposedSize.Height - reservedHeight)); size.Height += reservedHeight; return size; } #endregion #region 事件处理方法 //展开收起 private void ckbToggle_CheckedChanged(object sender, EventArgs e) { this.SuspendLayout(); if (ckbToggle.Checked) { plButtonsZone.SendToBack(); lbMsg.SendToBack(); lbMsg.Dock = DockStyle.Top; plButtonsZone.Dock = DockStyle.Top; ChangeFormHeight(ExpandHeight); plAttachZone.Visible = true; } else { ExpandHeight = plAttachZone.Height;//为再次展开记忆高度 plAttachZone.Visible = false; ChangeFormHeight(-plAttachZone.Height);//收起时直接取pl高度,不要取ExpandHeight plButtonsZone.SendToBack(); plButtonsZone.Dock = DockStyle.Bottom; lbMsg.Dock = DockStyle.Fill; } this.ResumeLayout(); } //按钮事件 private void button_Click(object sender, EventArgs e) { this.DialogResult = (DialogResult)((sender as Button).Tag); } //用户手工收完详细区则触发折叠 private void plAttachZone_Resize(object sender, EventArgs e) { if (ckbToggle.Checked && plAttachZone.Height == 0) { ckbToggle.Checked = false; } } #endregion #region 辅助+私有方法 /// /// 处理按钮相关 /// private void ProcessButtons() { this.ckbToggle.Visible = txbAttach.Text.Trim().Length != 0; //无详细信息就不显示展开按钮 int btnCount = 3; //按钮数量 switch (MessageButtons) //老实用case,可读点 { case MessageBoxButtons.AbortRetryIgnore: button1.Text = "中止(&A)"; button1.Tag = DialogResult.Abort; button2.Text = "重试(&R)"; button2.Tag = DialogResult.Retry; button3.Text = "忽略(&I)"; button3.Tag = DialogResult.Ignore; break; case MessageBoxButtons.OK: button1.Visible = false; button2.Visible = false; button3.Text = "确定"; button3.Tag = DialogResult.OK; btnCount = 1; break; case MessageBoxButtons.OKCancel: button1.Visible = false; button2.Text = "确定"; button2.Tag = DialogResult.OK; button3.Text = "取消"; button3.Tag = DialogResult.Cancel; btnCount = 2; break; case MessageBoxButtons.RetryCancel: button1.Visible = false; button2.Text = "重试(&R)"; button2.Tag = DialogResult.Retry; button3.Text = "取消"; button3.Tag = DialogResult.Cancel; btnCount = 2; break; case MessageBoxButtons.YesNo: button1.Visible = false; button2.Text = "是(&Y)"; button2.Tag = DialogResult.Yes; button3.Text = "否(&N)"; button3.Tag = DialogResult.No; btnCount = 2; break; case MessageBoxButtons.YesNoCancel: button1.Text = "是(&Y)"; button1.Tag = DialogResult.Yes; button2.Text = "否(&N)"; button2.Tag = DialogResult.No; button3.Text = "取消"; button3.Tag = DialogResult.Cancel; break; default: break; } //仅有OK和有取消按钮时设CancelButton if ((int)MessageButtons == 0 || ((int)MessageButtons & 1) == 1) { this.CancelButton = button3; } //处理默认按钮 if (btnCount == 1) { this.AcceptButton = button3; } else if (btnCount == 2) { this.AcceptButton = DefaultButton == MessageBoxDefaultButton.Button2 ? button3 : button2; } else { Button[] btnArray = { button1, button2, button3 }; this.AcceptButton = btnArray[Convert.ToInt32(DefaultButton) / 0x100]; } } /// /// 处理图标(含声音) /// private void ProcessIcon() { switch (MessageIcon) { //MessageBoxIcon.Information同样 case MessageBoxIcon.Asterisk: lbMsg.Icon = SystemIcons.Information; messageSound = "SystemAsterisk"; break; //MessageBoxIcon.Hand、MessageBoxIcon.Stop同样 case MessageBoxIcon.Error: lbMsg.Icon = SystemIcons.Error; messageSound = "SystemHand"; break; //MessageBoxIcon.Warning同样 case MessageBoxIcon.Exclamation: lbMsg.Icon = SystemIcons.Warning; messageSound = "SystemExclamation"; break; case MessageBoxIcon.Question: lbMsg.Icon = SystemIcons.Question; messageSound = "SystemAsterisk";//Question原本是没声音的,此实现让它蹭一下Information的 break; default: //MessageBoxIcon.None lbMsg.Icon = null; messageSound = "SystemDefault"; break; } } /// /// 计算窗体客户区最小高度 /// private int GetClientMinHeight() { return lbMsg.MinimumHeight + plButtonsZone.Height + Padding.Bottom; } /// /// 计算按钮区最小宽度 /// private int GetPanelButtonMinWidth() { int r = 20 /*左右Padding*/, visibleCount = -1 /*因为两个以上才会有间距*/; if (ckbToggle.Visible) { r += ckbToggle.Width; visibleCount++; } if (button1.Visible) { r += button1.Width * 3; visibleCount += 3; } else if (button2.Visible) { r += button2.Width * 2; visibleCount += 2; } else { r += button3.Width; visibleCount++; } if (visibleCount != -1) { r += visibleCount * 6; } //按钮间距 return r; } /// /// 改变窗体高度。内部有动画处理 /// /// 增量(负数即为减小高度) private void ChangeFormHeight(int increment) { int finalHeight = this.Height + increment; //正确的目标高度 if (!this.UseAnimate) //不使用动画 { this.Height = finalHeight; return; } const int step = 8; //帧数 for (int i = 0; i < step; i++) { if (i == step - 1) //最后一步直达目标 { this.Height = finalHeight; return; } this.Height += increment / step; Application.DoEvents(); //必要 Thread.Sleep(10); } } /// /// 播放系统事件声音 /// /// 之所以不用MessageBeep API是因为这货在srv08上不出声,所以用PlaySound代替 private static void PlaySystemSound(string soundAlias) { PlaySound(soundAlias, IntPtr.Zero, 0x10000 /*SND_ALIAS*/| 0x1 /*SND_ASYNC*/); } [DllImport("winmm.dll", CharSet = CharSet.Auto)] private static extern bool PlaySound([MarshalAs(UnmanagedType.LPWStr)] string soundName, IntPtr hmod, int soundFlags); #endregion #region 嵌套类 /// /// 基础面板 /// private class PanelBasic : Control { public PanelBasic() { SetStyle(ControlStyles.AllPaintingInWmPaint, false);//关键,不然其上的ToolBar不正常 SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//重要。不设置的话控件绘制不正常 SetStyle(ControlStyles.ContainerControl, true); SetStyle(ControlStyles.Selectable, false); } protected override void WndProc(ref Message m) { //屏蔽WM_ERASEBKGND。防止显示时在原位置快闪 //不能通过ControlStyles.AllPaintingInWmPaint=true屏蔽 //会影响其上的ToolBar if (m.Msg == 0x14) { return; } base.WndProc(ref m); } protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { //防Dock时面板短暂滞留在原位置 base.SetBoundsCore(x, y, width, height, specified | BoundsSpecified.Y | BoundsSpecified.Width); } } /// /// 消息呈现控件 /// private class MessageViewer : Control { const TextFormatFlags textFlags = TextFormatFlags.EndEllipsis //未完省略号 | TextFormatFlags.WordBreak //允许换行 | TextFormatFlags.NoPadding //无边距 | TextFormatFlags.ExternalLeading //行间空白。NT5必须,不然文字挤在一起 | TextFormatFlags.TextBoxControl; //避免半行 const int IconSpace = 5; //图标与文本间距 const float PreferredScale = 13;//最佳文本区块比例(宽/高) /// /// 最小高度。不要重写MinimumSize,那会在窗体移动和缩放时都会执行 /// public int MinimumHeight { get { return (this.Icon != null ? Math.Max(this.Icon.Height, this.FontHeight) : this.FontHeight) + Padding.Vertical; } } /// /// 获取或设置图标 /// public Icon Icon { get; set; } public MessageViewer() { this.SetStyle(ControlStyles.CacheText, true); this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); this.SetStyle(ControlStyles.Selectable, false); this.SetStyle(ControlStyles.ResizeRedraw, true); //重要 this.DoubleBuffered = true; //双缓冲 BackColor = Environment.OSVersion.Version.Major == 5 ? SystemColors.Control : Color.White; } //防Dock改变尺寸 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { base.SetBoundsCore(x, y, width, height, specified | BoundsSpecified.Size); } /// /// 计算合适的消息区尺寸 /// /// 该参数此处定义为此控件可设置的最大尺寸 /// 该方法对太长的单行文本有做比例优化处理,避免用户摆头幅度过大扭到脖子 public override Size GetPreferredSize(Size proposedSize) { if (proposedSize.Width < 10) { proposedSize.Width = int.MaxValue; } if (proposedSize.Height < 10) { proposedSize.Height = int.MaxValue; } int reservedWidth = Padding.Horizontal + (this.Icon == null ? 0 : (this.Icon.Width + IconSpace)); Size wellSize = Size.Empty; if (!string.IsNullOrEmpty(this.Text)) { //优化文本块宽高比例 Size size = TextRenderer.MeasureText(this.Text, this.Font, new Size(proposedSize.Width - reservedWidth, 0), textFlags);//用指定宽度测量文本面积 wellSize = Convert.ToSingle(size.Width) / size.Height >
PreferredScale / / the situation of being too broad and flat? Size.Ceiling (GetSameSizeWithNewScale (size, PreferredScale)): size; / / make sure the trailing line shows int lineHeight = TextRenderer.MeasureText ("", this.Font, new Size (int.MaxValue, 0), textFlags). Height;// is high in a single line, and Font.Height is unreliable int differ; wellSize.Height + = (differ = wellSize.Height% lineHeight) = 0? 0: (lineHeight-differ) } if (this.Icon! = null) {wellSize.Width + = this.Icon.Width + IconSpace; wellSize.Height = Math.Max (this.Icon.Height, wellSize.Height);} wellSize + = Padding.Size; / / should not exceed the specified size. The width above ensures that it does not exceed if (wellSize.Height > proposedSize.Height) {wellSize.Height = proposedSize.Height;} return wellSize;} / redraw / redraw / protected override void OnPaint (PaintEventArgs e) {Graphics g = e.Graphics; Rectangle rect = GetPaddedRectangle (); / / draw icon if (this.Icon! = null) {g.DrawIcon (this.Icon, Padding.Left, Padding.Top) / / move right to the text area rect.X + = this.Icon.Width + IconSpace; rect.Width-= this.Icon.Width + IconSpace; / / if there is too little text, center if vertically with the icon (this.Text.Length < 100) {Size textSize = TextRenderer.MeasureText (g, this.Text, this.Font, rect.Size, textFlags); if (textSize.Height