C# Windows窗体、设计器和Singleton

C# Windows窗体、设计器和Singleton,c#,winforms,C#,Winforms,我正在尝试使用Windows窗体和用户控件,到目前为止,这只会让我头疼。我不能使窗体或控件保持静态,因为设计器不喜欢这样,当我在窗体和控件上使用Singleton时,设计器仍然会向我抛出错误 我的FormMain: public partial class FormMain : Form { private static FormMain inst; public static FormMain Instance { get {

我正在尝试使用Windows窗体和用户控件,到目前为止,这只会让我头疼。我不能使窗体或控件保持静态,因为设计器不喜欢这样,当我在窗体和控件上使用Singleton时,设计器仍然会向我抛出错误

我的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的构造函数是公共的,则程序会运行,但当我将其更改为私有时,我现在在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 );
}