我想我已经回答了关于这个问题的前一个问题,看起来这就是你开始走这条路的原因。第一点是我并不是专门推荐这种模式,只是想教你更多关于软件开发人员如何管理范围的知识。
也就是说,您面临的问题并非无法克服。例如,您可以通过在运行时而不是在设计时抛出异常来阻碍公共构造函数,并修改 Program.cs 以使用静态实例而不是手动构造表单。
但是。
正如我在另一个问题中所说,更好的选择是更改架构,这样您就不需要库代码来直接操作 GUI。
您可以通过让 GUI 在它认为需要新数据(简单函数)时向库提出问题或在需要更改某些内容时通知 GUI 来做到这一点。任何一种方法都比让库直接处理标签要好。
一个好的起点应该是 MVC(模型-视图-控制器)架构,我在之前的回答中提到过。不过,最好让我们更详细地了解您的高级程序结构现在是什么样子。您在系统中使用的主要类是什么(不仅仅是您目前提到的那些)?每个人的主要职责是什么,每个人住在哪里?那么我们的建议可能会更具体一些。
编辑
因此,我根据您的评论模拟了一个可能的替代架构的快速演示。
我的项目中有以下内容:
FormMain (Form)
TitleScreen (UserControl)
InGameMenu (UserControl)
MainScreen (UserControl)
GameController (Class)
GameModel (Class)
我暂时没有使用Date 和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 的事件(在这种情况下,只是EventHandler 类型之一;您可以稍后创建更具表现力的事件类型,但让我们从简单的开始)。每当DisplayDate 更改时,都会触发此事件。当您查看属性定义时,您可以看到这是如何发生的:set 访问器(您不会从您的 GUI 调用)如果有人在听,就会引发事件。还有一些内部字段用于存储游戏状态和 GameController(不是您的 GUI)将根据需要调用的方法。
GameController 看起来像这样:
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 上下文中轻松使用这两个类。
这是否阐明了我和其他人一直试图解释的一些架构原则?