我正在尝试使用Windows窗体和用户控件,到目前为止,这只是头疼。我无法使表单或控件静态,因为设计师不喜欢这样,当我在表单和控件上使用Singleton时,设计师仍然会向我抛出错误。
My FormMain:
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的构造函数是公共程序运行,但是当我将其更改为private时,我现在在FormMain.Designer.cs中收到错误,说“'Adventurers_of_Wintercrest.UserControls.MainScreen.MainScreen()'由于其无法访问保护等级“。它指向这一行:
this.controlMainScreen = new Adventurers_of_Wintercrest.UserControls.MainScreen();
我认为这是设计师默认制作的类的实例。我应该抛弃设计师吗?或者有办法解决这个问题吗?或者是否有另一种方法可以在不使用Singleton的情况下访问类属性(因为我似乎无法使表单或控件静态)?任何帮助将不胜感激。
答案 0 :(得分:3)
如果要访问实例化表单的公共属性,则需要保留对每个表单的每个实例的引用。
一种方法是为每种类型的表单创建一个带有静态变量的类:
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 );
}
答案 1 :(得分:1)
我想我之前回答过一个关于此的问题,看起来这就是让你从这条路线开始的原因。第一点是我没有具体推荐这种模式,只是试图教你更多关于软件开发人员如何管理范围。
那就是说,你所面临的问题并非难以克服。例如,您可以通过在运行时而不是在设计时抛出异常来阻塞公共构造函数,并修改Program.cs以使用静态实例而不是手动构造表单。
但是
正如我在另一个问题中所说,更好的选择是改变架构,这样你就不需要你的库代码就可以直接操作GUI了。
您可以通过让GUI在认为需要新数据(简单功能)时询问库问题,或者在需要更改某些内容时通知GUI来执行此操作。任何一种方法都比让图书馆直接摆弄标签更好。
一个好的起点就像MVC(模型 - 视图 - 控制器)架构,我在之前的回答中提到过。但是,最好让我们更详细地了解一下您的高级程序结构现在的样子。你在系统中使用的主要课程是什么(不仅仅是你到目前为止提到的那些)?每个人的主要责任是什么,每个人住在哪里?然后我们的建议可能会更具体一些。
修改强>
所以,我根据你的评论嘲笑了一个可能的替代架构的快速演示。
我的项目中有以下内容:
FormMain (Form)
TitleScreen (UserControl)
InGameMenu (UserControl)
MainScreen (UserControl)
GameController (Class)
GameModel (Class)
目前我没有使用Date
和LoadSave
。
FormMain
只会在其上放置每个UserControl
的实例。没有特殊代码。
GameController
是一个单例(因为你已经尝试过使用这个模式,我认为你尝试使用它的工作版本会有所帮助)通过操作模型来响应用户输入。请注意:您不能直接从GUI(这是模型 - 视图 - 控制器的View部分)操作模型。它公开了一个GameModel
的实例,并有一堆方法可以让你执行加载/保存,结束转弯等游戏操作。
GameModel
是存储所有游戏状态的地方。在这种情况下,这只是一个日期和转弯计数器(好像这将是一个回合制游戏)。日期是一个字符串(在我的游戏世界中,日期以格式" Eschaton 23,3834.4")呈现,每个转弯都是一天。
TitleScreen和InGameMenu每个只有一个按钮,为清楚起见。理论上(不实现),TitleScreen允许您启动新游戏,InGameMenu允许您加载现有游戏。
因此,通过介绍,这里是代码。
GameModel:
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)";
}
}
需要注意的事项:您的模型有一个事件(在这种情况下,只是一个类型为EventHandler;您可以在以后创建更具表现力的事件类型,但让我们开始简单)称为DateChanged
。只要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可能需要在游戏状态下执行的所有高级操作:加载或保存一场比赛,结束转弯等等。
现在其余的很容易。
TitleScreen:
public partial class TitleScreen : UserControl
{
public TitleScreen()
{
InitializeComponent();
}
private void btnLoadNew(object sender, EventArgs e)
{
GameController.Instance.LoadNewGame();
}
}
InGameMenu:
public partial class InGameMenu : UserControl
{
public InGameMenu()
{
InitializeComponent();
}
private void btnLoadSaved_Click(object sender, EventArgs e)
{
GameController.Instance.LoadSavedGame("test");
}
}
注意这两个除了在Controller上调用方法之外什么都不做。容易。
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
事件。这样,可以在日期递增时通知它。我还在一个按钮中实现了另一个游戏功能(结束回合)。
如果你复制并运行它,你会发现游戏日期是从很多地方操纵的,并且标签总是正确更新。最重要的是,您的控制器和模型实际上并不了解View的任何内容 - 甚至它基于WinForms。您可以像在Windows Phone或Mono上下文中那样轻松地使用这两个类。
这是否澄清了我和其他人试图解释的一些架构原则?
答案 2 :(得分:0)
本质上问题是当应用程序运行时,它会尝试实例化主窗体。但是通过使用Singleton模式,你实际上禁止应用程序这样做。
在这里查看示例代码: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.aspx
您特别注意到这一部分:
[STAThread]
public static void Main()
{
// Start the application.
Application.Run(new Form1());
}
注意程序如何尝试实例化Form1
。你的代码说,不,我不是真的想要,因为你将构造函数标记为私有(同样适用于静态表单)。但这与Windows窗体的形成方式有关。如果您想要一个singleton
表单窗口,请不要再做了。就这么简单。