我想我已经回答了一个关于这个问题的问题,看起来这就是你开始走这条路的原因。第一点是我并不是专门推荐这种模式,只是想教你更多关于软件开发人员如何管理范围的知识。
也就是说,您面临的问题并非无法克服。例如,您可以通过在运行时而不是在设计时抛出异常来阻碍公共构造函数,并修改 Program.cs 以使用静态实例而不是手动构造表单。
但。
正如我在另一个问题中所说,更好的选择是更改架构,这样您就不需要库代码来直接操作 GUI。
您可以通过让 GUI 在它认为需要新数据(简单函数)时向库提出问题或在需要更改某些内容时通知 GUI 来做到这一点。任何一种方法都比让图书馆直接摆弄标签要好。
一个好的起点是类似于 MVC(模型-视图-控制器)架构,我在之前的回答中提到过。不过,最好让我们更详细地了解您的高级程序结构现在是什么样子。您在系统中使用的主要类是什么(不仅仅是您目前提到的那些)?每个人的主要职责是什么,每个人住在哪里?那么我们的建议可能会更具体一些。
编辑
因此,根据您的评论,我已经模拟了一个可能的替代架构的快速演示。
我的项目中有以下内容:
FormMain (Form)
TitleScreen (UserControl)
InGameMenu (UserControl)
MainScreen (UserControl)
GameController (Class)
GameModel (Class)
我暂时没有使用Date
and LoadSave
。
FormMain
只是有一个每个UserControl
放在它上面的实例。没有特殊代码。
GameController
是一个单例(因为您已经尝试使用此模式,我认为尝试使用它的工作版本会对您有所帮助),它通过操纵模型来响应用户输入。请注意:您不能直接从 GUI(这是模型视图控制器的视图部分)操作模型。它公开了一个实例,GameModel
并有一堆方法可以让你执行游戏操作,如加载/保存、结束回合等。
GameModel
是存储所有游戏状态的地方。在这种情况下,这只是一个日期和一个回合计数器(好像这将是一个回合制游戏)。日期是一个字符串(在我的游戏世界中,日期以“Eschaton 23, 3834.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
. 每当DisplayDate
更改时都会触发它。当您查看属性定义时,您可以看到这是如何发生的:set
如果有人在听,访问器(您不会从 GUI 调用)会引发事件。还有用于存储游戏状态和方法的内部字段GameController
(不是您的 GUI)将根据需要调用。
游戏控制器看起来像这样:
public class GameController
{
private static GameController instance;
public static GameController Instance
{
get
{
if (instance == null)
instance = new GameController();
return instance;
}
}
private GameController()
{
Model = new GameModel();
}
public void LoadSavedGame(string file)
{
// set all the state as saved from file. Since this could involve initialization
// code that could be shared with LoadNewGame, for instance, you could move this logic
// to a method on the model. Lots of options, as usual in software development.
Model.PretendToLoadAGame("Eschaton 93, 9776.9 (Debug: LoadSavedGame)");
}
public void LoadNewGame()
{
Model.PretendToLoadAGame("Eschaton 12, 9772.3 (Debug: LoadNewGame)");
}
public void SaveGame()
{
// to do
}
// Increment the date
public void EndTurn()
{
Model.IncrementDate();
}
public GameModel Model
{
get;
private set;
}
}
在顶部,您会看到单例实现。然后是构造函数,它确保始终有一个模型,以及加载和保存游戏的方法。GameModel
(在这种情况下,即使加载了新游戏,我也不会更改实例。原因是它GameModel
有事件,我不希望侦听器必须在这个简单的示例代码中拆线和重新连接它们。您可以决定如何您想自己解决这个问题。)请注意,这些方法基本上实现了您的 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();
}
}
这涉及更多一点,但不是很复杂。关键是,它将DateChanged
事件连接到模型上。这样可以在日期增加时通知它。我还在这里的一个按钮中实现了另一个游戏功能(结束回合)。
如果你复制它并运行它,你会发现游戏日期是从很多地方操纵的,并且标签总是正确更新的。最重要的是,您的控制器和模型实际上对视图一无所知——甚至不知道它基于 WinForms。您可以在 Windows Phone 或 Mono 上下文中轻松使用这两个类,就像其他任何东西一样。
这是否阐明了我和其他人一直试图解释的一些架构原则?