一、简介
在 Winform 开发中,多窗体的切换是一个常见的需求,比如登录成功后,切换至主界面,同时关闭登录界面,看似简单的需求,如果在别的框架里是很容易实现的,在 Winform 你如果按同样的逻辑写 this.Close(); form2.Show(); 你就会发现软件直接闪退了,因为 Winform 存在一个主线程概念,如果主线程关闭,那么当前软件所有的窗体都会随之关闭,这时你就会去查相关的资料,你会发现有的用的 form2.Show(); this.Hide(); 这种方式,这种方式其实也不是特别好,切换一两个窗体还好,如果窗体太多了,那代码就非常的难看。
在不久前,我写了一篇多窗体切换相关的帖子,地址如下:
C# Winform 多窗体切换方式一_winform登录成功后转向另一个窗体语句-优快云博客
有兴趣的可以去看看,这篇帖子用的方式是用的用户控件在窗体里面切换,并自动改变窗体的大小,从而看上去像切换了窗体一样,原理也非常的简单。
今天介绍多窗体切换的第二种,用一个窗体作为主窗体,启动后隐藏,并添加一个窗体的管理类,进行窗体的切换、开启或关闭。
效果:
二、实现效果
新建一个类库,取名 FormManager,添加一个窗体,取名 _MainForm,界面如下:
它本来就是没有任何界面,这个只是用来作为主线程,在后台运行,用来防止程序关闭用的。
代码:
using System;
using System.Windows.Forms;
public partial class _MainForm : Form
{
/// <summary>
/// 主程序启动后执行自定义的委托
/// </summary>
/// <param name="action"></param>
public _MainForm(Action action)
{
InitializeComponent();
StartAction = action;
}
/// <summary>
/// 主程序启动后显示自定义的界面
/// </summary>
/// <param name="formType"></param>
public _MainForm(Type formType)
{
InitializeComponent();
if(formType == null)
throw new ArgumentNullException("参数 formType 不能为空");
if (!typeof(Form).IsAssignableFrom(formType))
throw new ArgumentException($"当前参数 {formType.Name} 不是 Form 的派生类", nameof(formType));
this.Form1 = formType;
}
private Action StartAction;
private Type Form1;
private void _MainForm_Load(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
this.Visible = false;
//显示第一个窗体
if (this.Form1 != null)
FormManager.OpenForm(Form1);
StartAction?.Invoke();
}
}
这里有两个构造函数,第一个构造函数是传入一个委托,并在界面启动后,调用这个委托,你可以把启动那个界面,写在这个委托里,第二个构造函数是传入一个 Form 的 Type 作为显示的第一个程序。
添加一个类 FormManager.cs,这个类用来管理所有 Form 的启动和关闭。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
/// <summary>
/// 窗体管理
/// </summary>
public class FormManager
{
#region 字段
//所有打开的窗体
private static Dictionary<Type, Form> _openForms = new Dictionary<Type, Form>();
//主窗体
private static _MainForm _mainForm;
/// <summary>
/// 是否显示控制台日志
/// </summary>
public static bool IsShowErrorLog { get; set; }
/// <summary>
/// 错误日志委托
/// </summary>
public static event Action<string> ErrorLogAction;
#endregion
#region 主窗体
/// <summary>
/// 主窗体 实例化 / 获取实例
/// </summary>
/// <typeparam name="K">第一个窗体的类型</typeparam>
/// <returns>主窗体的实例</returns>
public static _MainForm MainFormControl<K>() where K : Form, new()
{
if (_mainForm == null)
{
Type formType = typeof(K);
_mainForm = new _MainForm(formType);
return _mainForm;
}
return _mainForm;
}
/// <summary>
/// 主窗体 实例化 / 获取实例
/// </summary>
/// <param name="action"></param>
/// <returns>主窗体的实例</returns>
public static _MainForm MainFormControl(Action action)
{
if (_mainForm == null)
{
_mainForm = new _MainForm(action);
return _mainForm;
}
return _mainForm;
}
/// <summary>
/// 关闭主窗体
/// </summary>
public static void CloseMainForm()
{
if (_mainForm == null)
{
AddErrorLog("[CloseMainForm]字段 _mainForm 不能为空");
return;
}
if (_mainForm.InvokeRequired)
_mainForm.Invoke(new Action(()=>_mainForm.Close()));
else
_mainForm.Close();
}
#endregion
#region 窗体是否打开
/// <summary>
/// 窗体是否打开
/// </summary>
/// <param name="formType">窗体的Type</param>
/// <returns>是否打开</returns>
public static bool IsFormOpen(Type formType)
{
if (formType == null)
return false;
return _openForms.ContainsKey(formType);
}
/// <summary>
/// 窗体是否打开
/// </summary>
/// <typeparam name="T">窗体的类型</typeparam>
/// <returns>是否打开</returns>
public static bool IsFormOpen<T>() where T : Form
{
Type formType = typeof(T);
return _openForms.ContainsKey(formType);
}
#endregion
#region 获取窗体对象
/// <summary>
/// 获取打开的 Form 窗体对象
/// </summary>
/// <typeparam name="T">窗体的类型</typeparam>
/// <returns>窗体的实例</returns>
public static T GetForm<T>() where T : Form
{
Type formType = typeof(T);
if (_openForms.ContainsKey(formType))
return (T)_openForms[formType];
return null;
}
#endregion
#region 切换界面
/// <summary>
/// 切换界面-1,针对无参数的构造函数
/// </summary>
/// <typeparam name="T">原窗体的类型</typeparam>
/// <typeparam name="K">新窗体的类型</typeparam>
public static void SwitchFrom<T, K>()
where T : Form, new()
where K : Form, new()
{
Type typeT = typeof(T);
Type typeK = typeof(K);
if (typeT == typeK)
{
AddErrorLog("[SwitchFrom1]参数 T 和 K 类型不能相同");
return;
}
if (_openForms.ContainsKey(typeK))
{
AddErrorLog("[SwitchFrom1]要跳转的窗体已经打开");
return;
}
OpenForm(typeK);
CloseForm(typeT);
}
/// <summary>
/// 切换界面-2,针对新窗体有参数的构造函数
/// </summary>
/// <typeparam name="T">原窗体的类型</typeparam>
/// <typeparam name="K">新窗体的类型</typeparam>
/// <param name="args">新窗体的构造函数参数</param>
public static void SwitchFrom<T, K>(params object[] args)
where T : Form, new()
where K : Form
{
Type typeT = typeof(T);
Type typeK = typeof(K);
if (typeT == typeK)
{
AddErrorLog("[SwitchFrom2]参数 T 和 K 类型不能相同");
return;
}
if (_openForms.ContainsKey(typeK))
{
AddErrorLog("[SwitchFrom2]要跳转的窗体已经打开");
return;
}
if (!CheckOpenError(typeK, args))
return;
OpenForm(typeK, args);
CloseForm(typeT);
}
#endregion
#region 打开窗体
/// <summary>
/// 打开窗体-1
/// </summary>
/// <param name="formType">窗体的Type</param>
public static void OpenForm(Type formType)
{
if (formType == null)
{
AddErrorLog("[OpenForm1]参数 formType 不能为空");
return;
}
if (!formType.IsSubclassOf(typeof(Form)))
{
AddErrorLog("[OpenForm1]参数 formType 不是一个 Form 类型");
return;
}
if (_mainForm == null)
{
AddErrorLog("[OpenForm1]字段 _mainForm 不能为空");
return;
}
if (_mainForm.InvokeRequired)
_mainForm.Invoke(new Action(() => ShowForm()));
else
ShowForm();
void ShowForm()
{
if (!_openForms.ContainsKey(formType))
{
var form = (Form)Activator.CreateInstance(formType);
form.FormClosed += (sender, e) => RemoveForm(formType);
_openForms[formType] = form;
form.Show();
}
else
{
Form existingForm = _openForms[formType];
if (existingForm.WindowState == FormWindowState.Minimized)
existingForm.WindowState = FormWindowState.Normal;
existingForm.BringToFront();
}
}
}
/// <summary>
/// 打开窗体-2
/// </summary>
/// <param name="formType">窗体的Type</param>
/// <param name="args">窗体的构造函数参数</param>
public static void OpenForm(Type formType, params object[] args)
{
if (!CheckOpenError(formType, args))
return;
if (_mainForm == null)
{
AddErrorLog("[OpenForm2]字段 _mainForm 不能为空");
return;
}
if (_mainForm.InvokeRequired)
_mainForm.Invoke(new Action(() => ShowForm()));
else
ShowForm();
void ShowForm()
{
if (!_openForms.ContainsKey(formType))
{
var form = (Form)Activator.CreateInstance(formType, args);
form.FormClosed += (sender, e) => RemoveForm(formType);
_openForms[formType] = form;
form.Show();
return;
}
else
{
Form existingForm = _openForms[formType];
if (existingForm.WindowState == FormWindowState.Minimized)
existingForm.WindowState = FormWindowState.Normal;
existingForm.BringToFront();
}
}
}
/// <summary>
/// 打开窗体-3
/// </summary>
/// <typeparam name="T">窗体的类型</typeparam>
/// <returns>窗体的实例</returns>
public static T OpenForm<T>() where T : Form, new()
{
Type formType = typeof(T);
if (_mainForm == null)
{
AddErrorLog("[OpenForm3]字段 _mainForm 不能为空");
return null;
}
if (_mainForm.InvokeRequired)
return (T)_mainForm.Invoke(new Func<T>(() => ShowForm()));
else
return ShowForm();
T ShowForm()
{
if (!_openForms.ContainsKey(formType))
{
T form = new T();
form.FormClosed += (sender, e) => RemoveForm(formType);
_openForms[formType] = form;
form.Show();
return form;
}
else
{
Form existingForm = _openForms[formType];
if (existingForm.WindowState == FormWindowState.Minimized)
existingForm.WindowState = FormWindowState.Normal;
existingForm.BringToFront();
return (T)existingForm;
}
}
}
/// <summary>
/// 打开窗体-4(有参数)
/// </summary>
/// <typeparam name="T">窗体的类型</typeparam>
/// <param name="args">窗体的构造函数参数</param>
/// <returns>窗体的实例</returns>
public static T OpenForm<T>(params object[] args) where T : Form
{
if (args == null || args.Length == 0)
{
AddErrorLog("[OpenForm4]参数 args 不能为空");
return null;
}
if (_mainForm == null)
{
AddErrorLog("[OpenForm4]字段 _mainForm 不能为空");
return null;
}
Type formType = typeof(T);
if (_mainForm.InvokeRequired)
return (T)_mainForm.Invoke(new Func<T>(() => ShowForm()));
else
return ShowForm();
T ShowForm()
{
if (!_openForms.ContainsKey(formType))
{
if (!CheckParametersConsistent(formType, args))
{
AddErrorLog("[OpenForm4]没有找到 args 与之对应的构造函数");
return null;
}
T form = (T)Activator.CreateInstance(formType, args);
form.FormClosed += (sender, e) => RemoveForm(formType);
_openForms[formType] = form;
form.Show();
return form;
}
else
{
Form existingForm = _openForms[formType];
if (existingForm.WindowState == FormWindowState.Minimized)
existingForm.WindowState = FormWindowState.Normal;
existingForm.BringToFront();
return (T)existingForm;
}
}
}
/// <summary>
/// 窗体关闭时移除窗体
/// </summary>
/// <param name="formType">窗体的Type</param>
private static void RemoveForm(Type formType)
{
_openForms.Remove(formType);
if (_openForms.Count == 0)
{
CloseMainForm();
}
}
/// <summary>
/// 检查打开前是否有错误
/// </summary>
/// <param name="formType">窗体的Type</param>
/// <param name="args">构造函数的参数</param>
/// <returns></returns>
private static bool CheckOpenError(Type formType, object[] args)
{
if (formType == null)
{
AddErrorLog("[CheckOpenError]参数 formType 不能为空");
return false;
}
if (args == null || args.Length == 0)
{
AddErrorLog("[CheckOpenError]参数 args 不能为空");
return false;
}
//已经打开的窗体,不必判断构造函数是否匹配
if (!_openForms.ContainsKey(formType))
{
if (!CheckParametersConsistent(formType, args))
{
AddErrorLog("[CheckOpenError]没有找到 args 与之对应的构造函数");
return false;
}
}
return true;
}
/// <summary>
/// 检查构造函数的参数和 args 是否匹配
/// </summary>
/// <param name="formType">窗体的Type</param>
/// <param name="args">构造函数的参数</param>
/// <returns></returns>
private static bool CheckParametersConsistent(Type formType, object[] args)
{
var constructors = formType.GetConstructors();
foreach (var constructor in constructors)
{
var parameters = constructor.GetParameters();
if (parameters.Length != args.Length)
continue;
// 检查每个参数的类型是否匹配
bool typesMatch = parameters
.Select((t, i) => new { ParameterType = t.ParameterType, Arg = args[i] })
.All(x => x.ParameterType.IsAssignableFrom(x.Arg.GetType()));
if (typesMatch)
return true;
}
return false;
}
#endregion
#region 关闭窗体
/// <summary>
/// 关闭窗体-1
/// </summary>
/// <typeparam name="T">窗体的类型</typeparam>
public static void CloseForm<T>() where T : Form
{
Type formType = typeof(T);
if (_openForms.ContainsKey(formType))
{
if (_mainForm == null)
{
AddErrorLog("[CloseForm1]字段 _mainForm 不能为空");
return;
}
if (_mainForm.InvokeRequired)
_mainForm.Invoke(new Action(() => CloseForm()));
else
CloseForm();
void CloseForm()
{
Form form = _openForms[formType];
form.Close();
}
}
}
/// <summary>
/// 关闭窗体-2
/// </summary>
/// <param name="formType">窗体的 Type</param>
public static void CloseForm(Type formType)
{
if (_openForms.ContainsKey(formType))
{
if (_mainForm == null)
{
AddErrorLog("[CloseForm2]字段 _mainForm 不能为空");
return;
}
if (_mainForm.InvokeRequired)
_mainForm.Invoke(new Action(() => CloseForm()));
else
CloseForm();
void CloseForm()
{
Form form = _openForms[formType];
form.Close();
}
}
}
#endregion
#region 其他
//显示错误日志
private static void AddErrorLog(string message)
{
if (IsShowErrorLog)
Console.WriteLine(message);
ErrorLogAction?.Invoke(message);
}
#endregion
}
那么当前的类库项目就完成了,接下来新建一个项目来测试这些功能
新建一个项目 Test,并添加三个窗体,项目结构如下:
Form1:
代码:
using System;
using System.Windows.Forms;
namespace Test1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FormManager.IsShowErrorLog = true;
}
private void button1_Click(object sender, EventArgs e)
{
FormManager.SwitchFrom<Form1, Form2>();
//FormManager.OpenForm<Form3>("1111");
}
}
}
Form2:
代码:
using System;
using System.Windows.Forms;
namespace Test1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
FormManager.SwitchFrom<Form2, Form3>("这是Form2传入的参数");
}
}
}
Form3:
代码:
using System;
using System.Windows.Forms;
namespace Test1
{
public partial class Form3 : Form
{
public Form3(string content)
{
InitializeComponent();
this.Content = content;
}
private string Content = string.Empty;
private void Form3_Load(object sender, EventArgs e)
{
label1.Text = Content;
}
}
}
Program 这里稍微做一下更改
using System;
using System.Windows.Forms;
namespace Test1
{
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(FormManager.MainFormControl<Form1>());
}
}
}
运行后的效果:
上面就是当前帖子所有的源码了,如果你需要 demo,也可以通过下面的链接下载:
https://download.youkuaiyun.com/download/qq_38693757/89720488
结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言
end