C# Windows窗体、设计器和Singleton
我正在尝试使用Windows窗体和用户控件,到目前为止,这只会让我头疼。我不能使窗体或控件保持静态,因为设计器不喜欢这样,当我在窗体和控件上使用Singleton时,设计器仍然会向我抛出错误 我的FormMain:C# Windows窗体、设计器和Singleton,c#,winforms,C#,Winforms,我正在尝试使用Windows窗体和用户控件,到目前为止,这只会让我头疼。我不能使窗体或控件保持静态,因为设计器不喜欢这样,当我在窗体和控件上使用Singleton时,设计器仍然会向我抛出错误 我的FormMain: public partial class FormMain : Form { private static FormMain inst; public static FormMain Instance { get {
public partial class FormMain : Form
{
private static FormMain inst;
public static FormMain Instance
{
get
{
if (inst == null || inst.IsDisposed)
inst = new FormMain();
return inst;
}
}
private FormMain()
{
inst = this;
InitializeComponent();
}
MainScreen.cs:
public partial class MainScreen : UserControl
{
private static MainScreen inst;
public static MainScreen Instance
{
get
{
if (inst == null || inst.IsDisposed)
inst = new MainScreen();
return inst;
}
}
private MainScreen()
{
inst = this;
InitializeComponent();
}
如果MainScreen的构造函数是公共的,则程序会运行,但当我将其更改为私有时,我现在在FormMain.Designer.cs中收到一个错误,其中说“'Adventurers\u of_Wintercrest.UserControls.MainScreen.MainScreen()”由于其保护级别而无法访问”。它指向这条线:
this.controlMainScreen = new Adventurers_of_Wintercrest.UserControls.MainScreen();
我认为这是设计器默认创建的类的实例。我应该抛弃设计师吗?还是有办法解决这个问题?或者有没有另一种方法不使用Singleton就可以访问类属性(因为我似乎无法使窗体或控件成为静态的)?任何帮助都将不胜感激。本质上,问题是当应用程序运行时,它将尝试实例化主窗体窗口。但是通过使用单例模式,您实际上禁止应用程序这样做 请看下面的示例代码: 您会特别注意到这一部分:
[STAThread]
public static void Main()
{
// Start the application.
Application.Run(new Form1());
}
注意程序是如何试图实例化
Form1
。你的代码说,不,我真的不想这样,因为你把构造函数标记为私有的(静态表单也是如此)。但这与windows窗体的工作原理背道而驰。如果你想要一个singleton
表单窗口,就不要再做了。就这么简单。本质上,问题是当应用程序运行时,它将尝试实例化主窗体窗口。但是通过使用单例模式,您实际上禁止应用程序这样做
请看下面的示例代码:
您会特别注意到这一部分:
[STAThread]
public static void Main()
{
// Start the application.
Application.Run(new Form1());
}
注意程序是如何试图实例化
Form1
。你的代码说,不,我真的不想这样,因为你把构造函数标记为私有的(静态表单也是如此)。但这与windows窗体的工作原理背道而驰。如果你想要一个singleton
表单窗口,就不要再做了。就这么简单。我想我已经回答了前面的一个问题,看起来这就是你开始走这条路的原因。第一点是,我不是特别推荐这种模式,只是想教你更多关于软件开发人员如何管理范围的知识
也就是说,你所面临的问题并非不可逾越。例如,您可以通过在运行时而不是在设计时抛出异常来阻碍公共构造函数,并修改Program.cs以使用静态实例而不是手动构造表单
但是
正如我在另一个问题中所说的,更好的选择是更改体系结构,这样您就不需要库代码来直接操作GUI
您可以通过让GUI在认为需要新数据(简单函数)时询问库问题,或者在需要更改时通知GUI来实现。这两种方法都比让库直接处理标签要好
一个好的起点应该是类似于MVC(model-view-controller)架构的东西,我在前面的回答中提到了这一点。不过,最好让我们更详细地了解一下您的高级程序结构。您在系统中使用的主要类是什么(不仅仅是您提到的那些)?每个人的主要责任是什么?每个人住在哪里?然后我们的建议可以更具体一些
编辑
因此,根据您的评论,我已经模拟了一个可能的替代体系结构的快速演示
我的项目中包含以下内容:
FormMain(表单)
标题屏幕(用户控制)
ingamenu(用户控制)
main屏幕(用户控制)
GameController(类)
GameModel(类)
日期
和加载保存
FormMain
只是在其上放置了每个UserControl
的一个实例。没有特殊代码
GameController
是一个通过操纵模型响应用户输入的单例(因为您已经尝试使用此模式,我认为尝试使用其工作版本会对您有所帮助)。请注意:您不能直接从GUI(模型视图控制器的视图部分)操作模型。它公开了GameModel
的一个实例,并提供了一系列方法,让您可以执行加载/保存、结束回合等游戏操作
GameModel
是存储所有游戏状态的地方。在本例中,这只是一个日期和回合计数器(好像这将是一个基于回合的游戏)。日期是一个字符串(在我的游戏世界中,日期以“Eschaton 233834.4”的格式显示),每个回合都是一天
为了清晰起见,TitleScreen和InGameMenu都只有一个按钮。理论上(而非实现上),TitleScreen允许您启动新游戏,InGameMenu允许您加载现有游戏
因此,随着介绍的方式,这里的代码
游戏模式:
public class GameModel
{
string displayDate = "Eschaton 23, 3834.4 (default value for illustration, never actually used)";
public GameModel()
{
// Initialize to 0 and then increment immediately. This is a hack to start on turn 1 and to have the game
// date be initialized to day 1.
incrementableDayNumber = 0;
IncrementDate();
}
public void PretendToLoadAGame(string gameDate)
{
DisplayDate = gameDate;
incrementableDayNumber = 1;
}
public string DisplayDate
{
get { return displayDate; }
set
{
// set the internal value
displayDate = value;
// notify the View of the change in Date
if (DateChanged != null)
DateChanged(this, EventArgs.Empty);
}
}
public event EventHandler DateChanged;
// use similar techniques to handle other properties, like
int incrementableDayNumber;
public void IncrementDate()
{
incrementableDayNumber++;
DisplayDate = "Eschaton " + incrementableDayNumber + ", 9994.9 (from turn end)";
}
}
需要注意的事项:您的模型有一个名为DateChanged
的事件(在本例中,仅为EventHandler类型之一;您可以稍后创建更具表现力的事件类型,但让我们从简单开始)。只要DisplayDate
发生更改,就会触发此命令。当您查看属性定义时,您可以看到这是如何发生的:如果有人在侦听,set
访问器(您不会从GUI调用它)将引发事件。还有
public partial class TitleScreen : UserControl
{
public TitleScreen()
{
InitializeComponent();
}
private void btnLoadNew(object sender, EventArgs e)
{
GameController.Instance.LoadNewGame();
}
}
public partial class InGameMenu : UserControl
{
public InGameMenu()
{
InitializeComponent();
}
private void btnLoadSaved_Click(object sender, EventArgs e)
{
GameController.Instance.LoadSavedGame("test");
}
}
public partial class MainScreen : UserControl
{
public MainScreen()
{
InitializeComponent();
GameController.Instance.Model.DateChanged += Model_DateChanged;
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Model_DateChanged(object sender, EventArgs e)
{
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Instance_CurrentGameChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void btnEndTurn_Click(object sender, EventArgs e)
{
GameController.Instance.EndTurn();
}
}
class FormReferenceHolder
{
public static Form1 form1;
public static Form2 form2;
}
class FormReferenceHolder
{
private static Form1 form1;
public static Form1 Form1
{
get
{
if (form1 == null) form1 = new Form1();
return form1 ;
}
}
}
static void Main()
{
Application.Run(FormReferenceHolder.Form1 );
}